This article will explain how to write unit tests with dependency injection (DI) using Dagger 2. I will assume you are already familiar with Mockito and Dagger 2.
Quick tip before getting started, when using Dagger 2 in a library project, instead of an application, make sure you add the following lines to your build script, if not you will not be able to use DI for unit testing.
apply plugin: 'com.android.library' apply from: 'jacoco-gradle.gradle' android { // ... } // Add these 3 lines android.libraryVariants.all { def aptOutputDir = new File(buildDir, "generated/source/apt/${it.unitTestVariant.dirName}") it.unitTestVariant.addJavaSourceFoldersToModel(aptOutputDir) } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) // ... }
Without these 3 line you will not be able to get your annotation processor to generate DI code for your unit tests. This took me an annoyingly long time to figure out when i first starting using Dagger DI with my unit tests. The files placed in test source sets are not added to the Java model, therefore not recognizable by Android Studio. This is a workaround until it is fixed.
The dagger dependencies are as follows:
dependencies { / ... compile 'com.google.dagger:dagger:2.11' annotationProcessor 'com.google.dagger:dagger-compiler:2.11' androidTestCompile "com.google.dagger:dagger:2.11" androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:2.11" testCompile "com.google.dagger:dagger:2.11" testAnnotationProcessor "com.google.dagger:dagger-compiler:2.11" / ... }
Im my opinion, one of the easiest ways to use DI for your unit tests is to write a BaseUnitTest class, that will be extended by all of my unit tests that require DI.
From now on, we will focus on testing a ProfileManager class with a basic Dagger 2 setup. First lets take a look at the setup and the ProfileManager class before we get to the testing. If you don’t care about the setup you can skip this part.
Lets take a look at our real component (MyComponent) class
@Singleton @Component (modules = {MyApiModule.class, /** other modules to inject **/}) interface MyComponent { // ... }
Lets take a look at the real MyApiModule
@Module class MyApiModule { @Singleton @Provides MyApi providesMyApi(ApiManager apiManager) { return apiManager.getApi(); } }
Nothing out of the ordinary.
Now lets take a look at our ProfileManager class which requires the api to be injected for later usage.
class ProfileManager { @Inject MyApi myApi; // ... public ProfileManager() { Injector.INSTANCE.inject(this); } }
So far so good, we have all of our basic Dagger setup. The component, the api module, and the injector setup, with a ProfileManager injecting itself using the injector to get access to the MyApi class which is generated from the ApiManager class.
How do we use this in our unit tests? In our test directory, we will create a BaseUnitTest class which will contain an internal interface (doesn’t have to be internal, but it will be for this example) which mirrors the modules used in the real MyComponent interface, and extends the real MyComponent interface as well.
class BaseUnitTest { // ... @Singleton @Component (modules = {MyApiModule.class, /** other real modules to inject **/}) interface TestComponent extends BeaconSDKComponent { // ... } // ... }
Now, inside our BaseUnitTest class lets create a setUp method to be overwritten by all of our child test classes. Inside of this setUp method we will build our component just as we would in our real injector class. The end result would look like this:
class BaseUnitTest { // package private component to use in our test classes TestComponent component; @Singleton @Component (modules = {MyApiModule.class, /** other real modules to inject **/}) interface TestComponent extends BeaconSDKComponent { // ... } @Before public void setUp() throws Exception { // some other code, even a mocked context if needed (Mockito.mock(Context.class)) to use in the other modules component = DaggerBaseUnitTest_TestComponent.builder() .myApiModule(new MyApiModule()) // other modules .build(); } }
Great, so now that all the dagger setup is complete for our unit tests, lets see how we can use this!
Again, in our test directory we will create a test MyApiTestModule class to represent our mocked api module.
We want to be able to mock network calls, and the network call results. To do this we create a new class which extends the MyApiModule class and we override the provides method which returns a mocked instance of our MyApi class.
class MyApiTestModule extends MyApiModule { @Override MyApi providesMyApi() { return Mockito.mock(MyApi.class); } }
Now we add this new test module in our BaseUnitTest setUp method.
class BaseUnitTest { TestComponent component; @Singleton @Component (modules = {MyApiModule.class, /** other real modules to inject **/}) interface TestComponent extends MyComponent { // ... } @Before public void setUp() throws Exception { // ... component = DaggerBaseUnitTest_TestComponent.builder() .myApiModule(new MyApiTestModule()) // ... .build(); } }
Great! now we are all done with all of our test DI setup. Lets create our ProfileManagerTest class in our test directory, like any other unit test.
@RunWith (MockitoJUnitRunner.class) public class ProfileManagerTest extends BaseUnitTest { @Inject MyApi api; // ... @Before public void setUp() throws Exception { super.setUp(); // required Injector.INSTANCE.setComponent(component); // component comes from our BaseUnitTest class, see above component.inject(this); // we inject this test class } // ... }
Once this is done, we are pretty much done, now we can mock responses for our injected api.
@RunWith (MockitoJUnitRunner.class) public class AmlMapBuilderTest extends BaseUnitTest { @Inject MyApi api; // ... @Before public void setUp() throws Exception { super.setUp(); Injector.INSTANCE.setComponent(component); component.inject(this); } @Test public void getProfileInfoSuccess() throws Exception { Mockito.when(api.getProfileInfoSuccess()).thenReturn(/** Some stuff that makes this test pass **/); api.getProfileInfoSuccess(); // make the call, no real network call will be made, this is all mocked, see the MyApiTestModule class above. Mockito.verify(/** that some class was called **/).withSomeMethod(/** and its parameters **/); // do other test stuff that makes you happy. } }
That’s all it takes. Every time you want to mock some injected dependency, create a test module, pass it in to your test component and you can mock all functionality in your unit tests. I hope this was helpful, please feel free to leave any comments below.