I read this great post by Artem Zinnatullin on How to mock dependencies in Unit, Integration and Functional tests; Dagger, Robolectric and Instrumentation. The part I like the best is to use a different application in tests to provide different dependencies, and I decided to try it with Espresso.
Mock application via custom test runner
My current approach to dependency injection is to expose a setComponent
function in my application for tests to supply the test component, which is not great because ideally the application should not have test-specific code.
The new approach is to subclass the application in the androidTest
folder and load it during tests via a custom test runner.
public class DemoApplication extends Application { private final DemoComponent component = createComponent(); protected DemoComponent createComponent() { return DaggerDemoApplication_ApplicationComponent.builder() .clockModule(new ClockModule()) .build(); } public DemoComponent component() { return component; } }
In the application we initialize a DemoComponent
with createComponent()
and stash it in a final
variable to be used later.
public class MockDemoApplication extends DemoApplication { @Override protected DemoComponent createComponent() { return DaggerMainActivityTest_TestComponent.builder() .mockClockModule(new MockClockModule()) .build(); } }
For testing, we subclass our application and override createComponent
to supply the test component instead.
To use the mock application in tests, we need a custom test runner:
public class MockTestRunner extends AndroidJUnitRunner { @Override public Application newApplication( ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { return super.newApplication( cl, MockDemoApplication.class.getName(), context); } }
We give it MockDemoApplication.class.getName()
as the class name so the test runner will load the mock application instead of the main one.
Per application vs per test
This approach is slightly different from setComponent
because we initialize the test component only once, rather than before each test method. Make sure you clear the state of your test modules before each test so run each test in from a clean slate.
Source code
I have updated two of my repositories to use this approach:
- android-test-demo: Minimal example to demonstrate the concepts.
- friendspell: Real app showing how to clear the state before each test.
Like this article? Take a look at the outline of my Espresso book and fill in this form to push me to write it! Also check out the published courses: https://gumroad.com/chiuki