Skip to content

Instantly share code, notes, and snippets.

@jon-adams
Created March 23, 2016 20:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jon-adams/b6994a8e7a3f0aac0ba1 to your computer and use it in GitHub Desktop.
Save jon-adams/b6994a8e7a3f0aac0ba1 to your computer and use it in GitHub Desktop.
More robust Android LoaderTestCase implementation (works with support library Loaders, cancellation support, extends instrumentation so the Context is available)
package com.nfhsnetwork.stream;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.Nullable;
import android.support.v4.content.Loader;
import android.test.ActivityInstrumentationTestCase2;
import java.util.concurrent.ArrayBlockingQueue;
/**
* A convenience class for testing {@link Loader}s. This test case
* provides a simple way to synchronously get the result from a Loader making
* it easy to assert that the Loader returns the expected result.
* <p/>
* This is a slightly modified version of {@link android.test.LoaderTestCase} that supports
* returning null from {@link #getLoaderResultSynchronously(Loader)} on cancellation (instead of
* blocking forever as the default LoaderTestCase does), supports the Android support library
* loaders, and extends {@link ActivityInstrumentationTestCase2} so that an application context is
* available for integration testing of the loader.
*/
public class SupportLoaderTestCase<T extends Activity> extends ActivityInstrumentationTestCase2<T> {
private final static Cancelled cancelled = new Cancelled();
static {
// Force class loading of AsyncTask on the main thread so that it's handlers are tied to
// the main thread and responses from the worker thread get delivered on the main thread.
// The tests are run on another thread, allowing them to block waiting on a response from
// the code running on the main thread. The main thread can't block since the AysncTask
// results come in via the event loop.
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... args) {
return null;
}
@Override
protected void onPostExecute(Void result) {
}
};
}
public SupportLoaderTestCase(Class<T> activityClass) {
super(activityClass);
}
/**
* Runs a Loader synchronously and returns the result of the load. The loader will
* be started, stopped, and destroyed by this method so it cannot be reused.
*
* @param loader the loader to run synchronously
* @return the result from the loader; may be null only if the loader was canceled
*/
@Nullable
public <T> T getLoaderResultSynchronously(final Loader<T> loader) {
// The test thread blocks on this queue until the loader puts it's result in
final ArrayBlockingQueue<Object> queue = new ArrayBlockingQueue<Object>(1);
// This callback runs on the "main" thread and unblocks the test thread
// when it puts the result into the blocking queue
final Loader.OnLoadCompleteListener<T> listener = new Loader.OnLoadCompleteListener<T>() {
@Override
public void onLoadComplete(Loader<T> completedLoader, T data) {
// Shut the loader down
completedLoader.unregisterListener(this);
completedLoader.stopLoading();
completedLoader.reset();
// Store the result, unblocking the test thread
queue.add(data);
}
};
final Loader.OnLoadCanceledListener<T> cancelListener =
new Loader.OnLoadCanceledListener<T>() {
@Override
public void onLoadCanceled(Loader<T> canceledLoader) {
// Shut the loader down
canceledLoader.unregisterOnLoadCanceledListener(this);
canceledLoader.stopLoading();
canceledLoader.reset();
// Remove the expectation of a result, unblocking the test thread
queue.add(cancelled);
}
};
// This handler runs on the "main" thread of the process since AsyncTask
// is documented as needing to run on the main thread and many Loaders use
// AsyncTask
final Handler mainThreadHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
loader.registerListener(0, listener);
loader.registerOnLoadCanceledListener(cancelListener);
loader.startLoading();
}
};
// Ask the main thread to start the loading process
mainThreadHandler.sendEmptyMessage(0);
// Block on the queue waiting for the result of the load to be inserted
T result;
while (true) {
try {
Object queueObj = queue.take();
if (cancelled.equals(queueObj)) {
return null;
}
//noinspection unchecked because the above private code only fills with instance of cancelled or with the type-checked delivery result
result = (T) queueObj;
break;
} catch (InterruptedException e) {
throw new RuntimeException("waiting thread interrupted", e);
}
}
return result;
}
/**
* Used to uniquely check if the response was actually a loader cancellation, not a valid result
*/
private static class Cancelled {
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment