I must be missing something here. The JavaDoc of ActivityUnitTestCase
suggest that this test case tests an Activity in isolation from the system:
This class provides isolated testing of a single activity. The activity under test will be created with minimal connection to the system infrastructure, and you can inject mocked or wrappered versions of many of Activity's dependencies.
I was assuming that includes not actually starting the application. Moreover, it exposes a setApplication
helper that one can presumably use to inject a mock application.
However, any ActivityUnitTestCase
I start launches the (actual) application and calls its onCreate
method. More precisely, the InstrumentationTestRunner
seems to be doing that, and doing so even before I get a chance to setApplication
in my test's setUp
method! I didn't even notice that for quite a while, since it seems to happen at a point during test suite launch where not even Eclipse breakpoints are reached, but writing to the logs in onCreate
reveals that it's actually called.
This is completely beyond me. Why would I want to use a mock app object when Android's test runner instantiates and executes the actual application anyway? This is even more problematic considering that the instrumentation runner runs in its own thread, and spawns the main application thread when doing so. This means there is a race condition between the test being executed and Application.onCreate
being called. If you do anything in there that could affect your tests, e.g. writing to a shared preference file, then you're completely screwed, since your tests will randomly fail.
Am I missing something or is this simply a gross oversight in the test framework?
UPDATE
This seems to affect ApplicationTestCase
as well. Before my test case is even started, I can reach a breakpoint in my application class' onCreate
. We start a fire-and-forget AsyncTask in there, which will randomly fail because I get no chance to mock it out (remember, that's before setUp
is called on my test case). Here is the stack trace I see during this obscure invocation of onCreate:
Thread [<1> main] (Suspended (breakpoint at line 86 in QypeRadar))
QypeRadar.onCreate() line: 86
InstrumentationTestRunner(Instrumentation).callApplicationOnCreate(Application) line: 969
ActivityThread.handleBindApplication(ActivityThread$AppBindData) line: 4244
ActivityThread.access$3000(ActivityThread, ActivityThread$AppBindData) line: 125
ActivityThread$H.handleMessage(Message) line: 2071
ActivityThread$H(Handler).dispatchMessage(Message) line: 99
Looper.loop() line: 123
ActivityThread.main(String[]) line: 4627
Method.invokeNative(Object, Object[], Class, Class[], Class, int, boolean) line: not available [native method]
Method.invoke(Object, Object...) line: 521
ZygoteInit$MethodAndArgsCaller.run() line: 868
ZygoteInit.main(String[]) line: 626
NativeStart.main(String[]) line: not available [native method]
Why does the test runner callApplicationOnCreate
even though the docs clearly state:
The test case will not call onCreate() until your test calls createApplication(). This gives you a chance to set up or adjust any additional framework or test logic before onCreate().
That's a flat out lie--it doesn't give me the chance!
Roboguice had the same issue. Check it here.
I'm doing tests with dagger, so probably this is your case too, since you probably want to just inject and do not call whatever is in Application.onCreate, so this one is works fine for me (api17+):
private Context mContext;
private Application mApplication;
@Override
protected void setUp() throws Exception {
super.setUp();
mContext = new ContextWrapper(getInstrumentation().getTargetContext()) {
@Override
public Context getApplicationContext() {
return mApplication;
}
};
mApplication = new MyAppMock();
mApplication.attachBaseContext(mContext);
setApplication(app);
}
public void testActivityCreated() {
Intent intent = AboutActivity.createIntent(mContext);
setActivityContext(mContext);
startActivity(intent, null, null);
assertNotNull(getActivity());
}
For < api16 you need to use reflection and call Application.attach(context)
instead Application.attachBaseContext()
to set Application.mLoadedApk
, otherwise it will crash.
I have put everything together and made demo app that shows how to test with dagger: https://github.com/vovkab/dagger-unit-test
It also shows how to mock your application, works for any android version.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With