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
Inline coding questions will not be answsered. Instead, ask on StackOverflow and put the link in the comment.
is this instead of a test dagger module?
ReplyDeleteYou still need test dagger modules and component. This is a way to load the test component for use during testing.
DeleteHi, I am working on a JUnit rule to simplify Dagger and Mockito integration, it's on github here: https://github.com/fabioCollini/DaggerMock
DeleteThe rule creates a module subclass on the fly using the fields defined in the test class. Using it you don't need to define a test module and a test component.
It's still an early version but it already works, any feedback is welcome!
So is there a way to do that without using Dagger 2 and reflection?
ReplyDeleteThere is no reflection in this approach. Dagger 1 uses reflection, not Dagger 2. If you prefer not to use Dagger, you can use build flavors to provide the mock. See http://code-labs.io/codelabs/android-testing for an example.
Delete