Thursday, December 31, 2015

Mock Application in Espresso for Dependency Injection

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:

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

5 comments:

Inline coding questions will not be answsered. Instead, ask on StackOverflow and put the link in the comment.

  1. is this instead of a test dagger module?

    ReplyDelete
    Replies
    1. You still need test dagger modules and component. This is a way to load the test component for use during testing.

      Delete
    2. Hi, I am working on a JUnit rule to simplify Dagger and Mockito integration, it's on github here: https://github.com/fabioCollini/DaggerMock
      The 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!

      Delete
  2. So is there a way to do that without using Dagger 2 and reflection?

    ReplyDelete
    Replies
    1. There 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