unit Why does AndroidTestCase.getContext().getApplicationContext() return null?




mockito android instrumentation test (2)

Instrumentation runs in a separate thread from the main app thread, so that it can execute without blocking or disrupting (or being blocked by) the main thread. If you need to synchronize with the main thread, use for example: Instrumentation.waitForIdleSync()

In particular, the Application object as well as all other top-level classes like Activity are initialized by the main thread. Your instrumentation thread is running at the same time those are initializing. This if you are touching any of those objects and are not implementing your own measures of thread safety, you should probably have such code run on the main thread such as via: Instrumentation.runOnMainSync(java.lang.Runnable)

UPDATE 2/13/2012: Accepted an answer, explained that this behavior is a bug, and noted that it appears to have disappeared on emulators better than v 1.6, which makes it a non-issue for most of us. The workaround is simply to loop/sleep until getContext().getApplicationContext() returns non-null. END UPDATE

As per android.app.Application javadoc, I defined a singleton (called Database) that all of my activities access for state and persistent data, and Database.getDatabase(Context) gets the application context via Context.getApplicationContext(). This setup works as advertised when activities pass themselves to getDatabase(Context), but when I run a unit test from an AndroidTestCase, the getApplicationContext() call often returns null, though the longer the test, the more frequently it returns a non-null value.

The following code reproduces the null within an AndroidTestCase -- the singleton isn't necessary for the demonstration.

First, to log app-instantiation messages, in the app-under-test I defined MyApp and added it to the manifest.

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Log.i("MYAPP", "this=" + this);
        Log.i("MYAPP", "getAppCtx()=" + getApplicationContext());
    }
}

Next, I defined a test case to report on AndroidTestCase.getContext() 4 times, separated by some sleeps and a getSharedPreferences() call:

public class DatabaseTest extends AndroidTestCase {
    public void test_exploreContext() {
        exploreContexts("XPLORE1");
        getContext().getSharedPreferences("foo", Context.MODE_PRIVATE);
        exploreContexts("XPLORE2");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        exploreContexts("XPLORE3");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        exploreContexts("XPLORE4");
    }
    public void exploreContexts(String tag) {
        Context testContext = getContext();
        Log.i(tag, "testCtx=" + testContext + 
                " pkg=" + testContext.getApplicationInfo().packageName);
        Log.i(tag, "testContext.getAppCtx()=" + testContext.getApplicationContext());
        try {
            Context appContext = testContext.createPackageContext("com.foo.android", 0);
            ApplicationInfo appInfo = appContext.getApplicationInfo();
            Log.i(tag, "appContext=" + appContext +
                    " pkg=" + appContext.getApplicationInfo().packageName);
            Log.i(tag, "appContext.getAppCtx()=" + appContext.getApplicationContext());
        } catch (NameNotFoundException e) {
            Log.i(tag, "Can't get app context.");
        }
    }
}

And this is a chunk of the resulting logCat (1.6 emulator on SDK11 WinXP via Eclipse):

INFO/TestRunner(465): started: test_exploreContext(test.foo.android.DatabaseTest)
INFO/XPLORE1(465): [email protected] pkg=com.foo.android
INFO/XPLORE1(465): testContext.getAppCtx()=null
INFO/XPLORE1(465): [email protected] pkg=com.foo.android
INFO/XPLORE1(465): appContext.getAppCtx()=null
INFO/XPLORE2(465): testCtx=android.a[email protected] pkg=com.foo.android
INFO/XPLORE2(465): testContext.getAppCtx()=null
INFO/XPLORE2(465): [email protected] pkg=com.foo.android
INFO/XPLORE2(465): appContext.getAppCtx()=null
INFO/MYAPP(465): [email protected]
INFO/MYAPP(465): getAppCtx()[email protected]
INFO/XPLORE3(465): [email protected] pkg=com.foo.android
INFO/XPLORE3(465): testContext.getAppCtx()[email protected]
INFO/XPLORE3(465): [email protected] pkg=com.foo.android
INFO/XPLORE3(465): appContext.getAppCtx()[email protected]
INFO/XPLORE4(465): [email protected] pkg=com.foo.android
INFO/XPLORE4(465): testContext.getAppCtx()[email protected]
INFO/XPLORE4(465): [email protected] pkg=com.foo.android
INFO/XPLORE4(465): appContext.getAppCtx()[email protected]
INFO/TestRunner(465): finished: test_exploreContext(test.foo.android.DatabaseTest)

Notice that getApplicationContext() returned null for a while, then started returning an instance of MyApp. I have not been able to get the exact same results in different runs of this test (that's how I ended up at 4 iterations, sleeps, and that call to getSharedPreferences() to try to goose the app into existence).

The chunk of LogCat messages above seemed most relevant, but the entire LogCat for that single run of that single test was interesting. Android started 4 AndroidRuntimes; the chunk above was from the 4th. Interestingly, the 3rd runtime displayed messages indicating that it instantiated a different instance of MyApp in process ID 447:

INFO/TestRunner(447): started: test_exploreContext(test.foo.android.DatabaseTest)
INFO/MYAPP(447): [email protected]
INFO/MYAPP(447): getAppCtx()[email protected]
INFO/TestRunner(447): finished: test_exploreContext(test.foo.android.DatabaseTest)

I assume that the TestRunner(447) messages are from a parent test thread reporting on its children in process 465. Still, the question is: why does Android let an AndroidTestCase run before its context is properly hooked up to an Application instance?

Workaround: One of my tests seemed to avoid nulls most of the time if I called getContext().getSharedPreferences("anyname", Context.MODE_PRIVATE).edit().clear().commit(); first, so I'm going with that.

BTW: If the answer is "it's an Android bug, why don't you file it; heck, why don't you fix it?" then I'd be willing to do both. I haven't taken the step of being a bug-filer or contributor yet -- maybe this is a good time.


As mentioned in the question and Dianne's answer (@hackbod), the Instrumentation runs on a separate thread. AndroidTestCase either has an implementation defect (missing synchronization) or it is not documented correctly. Unfortunately, there is no way to call Instrumentation.waitForIdleSync() from this particular test case class because the Instrumentation is not accessible from it.

This subclass can be used to add synchronization that polls getApplicationContext() until it returns a non-null value:

public class MyAndroidTestCase extends AndroidTestCase {

    @Override
    public void setContext(Context context) {
        super.setContext(context);

        long endTime = SystemClock.elapsedRealtime() + TimeUnit.SECONDS.toMillis(2);

        while (null == context.getApplicationContext()) {

            if (SystemClock.elapsedRealtime() >= endTime) {
                fail();
            }

            SystemClock.sleep(16);
        }
    }
}

The polling and sleep durations are based on experience and can be tuned if necessary.





android-context