Android testing using Dagger 2, Mockito and a custom JUnit rule

Dependency injection is a key concept to get testable code. Using dependency injection it’s easy to replace an object with a mock to change and verify the behavior of a system.

Dagger 2 is the dependency injection library used in many Android projects, in this post we’ll see how to take advantage of it to test an Android app.

Let’s see a very simple example, the MainService class uses other two classes to simulate a call to an external service and print the result:

public class MainService {
private RestService restService;
private MyPrinter printer;

@Inject public MainService(RestService restService,
MyPrinter printer) {
this.restService = restService;
this.printer = printer;
}

public void doSomething() {
String s = restService.getSomething();
printer.print(s.toUpperCase());
}
}

The doSomething method has no direct input and output but, thanks to dependency injection and Mockito, it’s not difficult to test it.

The implementation of the other classes is straightforward:

public class RestService {
public String getSomething() {
return "Hello world";
}
}
public class MyPrinter {
public void print(String s) {
System.out.println(s);
}
}

We want to test the MainService class in isolation, for this reason we don’t use Inject annotation in these two classes (we’ll see more details later in this post). We instantiate them in a Dagger module:

@Module
public class MyModule {
@Provides @Singleton public RestService provideRestService() {
return new RestService();
}

@Provides @Singleton public MyPrinter provideMyPrinter() {
return new MyPrinter();
}
}

We need a Dagger component to instantiate the MainService object and inject the Activity:

@Singleton
@Component(modules = MyModule.class)
public interface MyComponent {
MainService mainService();

void inject(MainActivity mainActivity);
}

JUnit test using Mockito

The MainService class can be easily tested in isolation using Mockito:

public class MainServiceTest {

@Rule public MockitoRule mockitoRule = MockitoJUnit.rule();

@Mock RestService restService;

@Mock MyPrinter myPrinter;

@InjectMocks MainService mainService;

@Test public void testDoSomething() {
when(restService.getSomething()).thenReturn("abc");

mainService.doSomething();

verify(myPrinter).print("ABC");
}
}

The MockitoRule usage is equivalent to the MockitoJUnitRunner, it invokes the static method MockitoAnnotations.initMocks to populate the annotated fields. Thanks to the InjectMocks annotation the mainService object is automatically created, the two mocks defined in the test are used as constructor arguments.

Dagger is not involved at all in this kind of test, this can be good because the test is very simple and it’s a real unit test.

Dagger 2 test

Sometimes we want to write an higher level test that uses Dagger to instantiate the objects. The simplest way to override an object is explained in this post written by Artem Zinnatullin. Following his suggestion we can define a TestModule that extends the original module and overrides the methods to return two mocks:

public class TestModule extends MyModule {
@Override public MyPrinter provideMyPrinter() {
return Mockito.mock(MyPrinter.class);
}

@Override public RestService provideRestService() {
return Mockito.mock(RestService.class);
}
}

We also need a TestComponent to inject the test object:

@Singleton
@Component(modules = MyModule.class)
public interface TestComponent extends MyComponent {
void inject(MainServiceDaggerTest test);
}

The test class contains three fields annotated with Inject annotation, in the setUp method we create the TestComponent and we use it to inject the test object to populate these fields:

public class MainServiceDaggerTest {

@Inject RestService restService;

@Inject MyPrinter myPrinter;

@Inject MainService mainService;

@Before public void setUp() {
TestComponent component = DaggerTestComponent.builder()
.myModule(new TestModule()).build();
component.inject(this);
}

@Test public void testDoSomething() {
when(restService.getSomething()).thenReturn("abc");

mainService.doSomething();

verify(myPrinter).print("ABC");
}
}

This test works correctly but there are some aspects that can be improved:

  • the restService and myPrinter fields contain two mocks but are annotated with Inject and not with Mock as in the previous test;
  • a test module and a test component are required to write and execute the test.

DaggerMock: a JUnit rule to override Dagger 2 objects

Dagger uses an annotation processor to analyze all the classes in the project looking for annotations, but the TestModule of the previous example doesn’t contain any Dagger annotation!

The basic idea of DaggerMock is to create a JUnit rule that dynamically creates a module subclass. The methods of this module subclass return the mocks defined in the test object. It’s not easy to explain it, let’s see the final result:

public class MainServiceTest {

@Rule public DaggerMockRule<MyComponent> mockitoRule =
new DaggerMockRule<>(MyComponent.class, new MyModule())
.set(component -> mainService = component.mainService());

@Mock RestService restService;

@Mock MyPrinter myPrinter;

MainService mainService;

@Test
public void testDoSomething() {
when(restService.getSomething()).thenReturn("abc");

mainService.doSomething();

verify(myPrinter).print("ABC");
}
}

In this example the rule dynamically creates a MyModule subclass that returns the mocks defined in the test instead of the real objects. This test is similar to the first test in this post (the one with InjectMocks annotation), the main difference is that now we are creating mainService field using Dagger. Other benefits of the DaggerMockRule usage are:

  • not all dependencies of the tested object must be defined in the test. The object defined in the Dagger config is used when a dependent object is not defined in the test;
  • it’s easy to override an object that it’s not directly used (for example when an object A references an object B that references an object C and we want to override just object C).

Espresso test

There are many posts about Dagger, Mockito and Espresso integration. For example this post written by Chiu-Ki Chan contains the most common solution to this problem.

Let’s see another example, an Activity that invokes the method of the previous example:

public class MainActivity extends AppCompatActivity {

@Inject MainService mainService;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

App app = (App) getApplication();
app.getComponent().inject(this);

mainService.doSomething();
//...
}
}

We can test this Activity using an ActivityTestRule, the test is similar to MainServiceDaggerTest (a TestComponent and a TestModule are used):

public class MainActivityTest {

@Rule public ActivityTestRule<MainActivity> activityRule =
new ActivityTestRule<>(MainActivity.class, false, false);

@Inject RestService restService;

@Inject MyPrinter myPrinter;

@Before
public void setUp() throws Exception {
EspressoTestComponent component =
DaggerEspressoTestComponent.builder()
.myModule(new EspressoTestModule()).build();

getApp().setComponent(component);

component.inject(this);
}
    private App getApp() {
return (App) InstrumentationRegistry.getInstrumentation()
.getTargetContext().getApplicationContext();
}
    @Test
public void testCreateActivity() {
when(restService.getSomething()).thenReturn("abc");

activityRule.launchActivity(null);

verify(myPrinter).print("ABC");
}
}

DaggerMock and Espresso

This test can be simplified using a DaggerMockRule, in the lambda expression we set the component in the application to override Dagger objects using the mocks:

public class MainActivityTest {

@Rule public DaggerMockRule<MyComponent> daggerRule =
new DaggerMockRule<>(MyComponent.class, new MyModule())
.set(component -> getApp().setComponent(component));
    @Rule public ActivityTestRule<MainActivity> activityRule = 
new ActivityTestRule<>(MainActivity.class, false, false);

@Mock RestService restService;

@Mock MyPrinter myPrinter;
    //...
}

The rule can be used in a Robolectric test too, an example is available in the project repository.

Custom rule

The same rule is often used in all the tests of a project, we can create a subclass to avoid copy and paste. For example the rule of the previous example can be written in a new class MyRule:

public class MyRule extends DaggerMockRule<MyComponent> {
public MyRule() {
super(MyComponent.class, new MyModule());
set(component -> getApp().setComponent(component));
}

private App getApp() {
return (App) InstrumentationRegistry.getInstrumentation()
.getTargetContext().getApplicationContext();
}
}

In some cases we want to override an object but we don’t need the reference in the test. For example in an Espresso test we don’t want to track the analytics events to a remote server, we can use a mock to fix it. To define a custom object we can invoke one of the following methods on the rule:

  • provides(Class<T> originalClass, T newObject): override the object of a class with a specific object;
  • provides(Class<T> originalClass, Provider<T> provider): similar to the previous method but useful for non singleton objects;
  • providesMock(Class<?>… originalClasses): overrides using a mock all the objects of the classes passed as arguments. It’s a shorthand for provide(MyObject.class, Mockito.mock(MyObject.class)).

An example of a custom rule that uses these methods is available in the CoseNonJaviste app:

public class CnjDaggerRule 
extends DaggerMockRule<ApplicationComponent> {
    public CnjDaggerRule() {
super(ApplicationComponent.class, new AppModule(getApp()));
provides(SchedulerManager.class,
new EspressoSchedulerManager());
providesMock(WordPressService.class, TwitterService.class);
set(component -> getApp().setComponent(component));
}

public static CoseNonJavisteApp getApp() {
return (CoseNonJavisteApp)
InstrumentationRegistry.getInstrumentation()
.getTargetContext().getApplicationContext();
}
}

The final version of the Espresso test is very simple (and you don’t need a TestComponent or a TestModule!):

public class MainActivityTest {

@Rule public MyRule daggerRule = new MyRule();

@Rule public ActivityTestRule<MainActivity> activityRule =
new ActivityTestRule<>(MainActivity.class, false, false);

@Mock RestService restService;

@Mock MyPrinter myPrinter;

@Test
public void testCreateActivity() {
when(restService.getSomething()).thenReturn("abc");

activityRule.launchActivity(null);

verify(myPrinter).print("ABC");
}
}

DaggerMock is an open source project available on GitHub, you can easily integrate it in your project using the JitPack repository.