I've been doing Android instrumentation testing with Dagger, Espresso and Mockito, and I love it. To commemorate the launch of Dagger 2 out of SNAPSHOT, I am sharing a demo repo with Dagger 2, Espresso 2 and Mockito:
https://github.com/chiuki/android-test-demo
Dagger Components
Dependency injection allows our app and test obtain different modules. This is very useful for creating repeatable test cases. The demo app displays today's date in the format yyyy-MM-dd
. We would like to test that against a known date, instead of depend on the actual date when we run the test.
In Dagger 2, a Component
provides modules for your whole app, and defines where to inject them.
public interface DemoComponent {
void inject(MainActivity mainActivity);
}
@Singleton
@Component(modules = ClockModule.class)
public interface ApplicationComponent extends DemoComponent {
}
@Singleton
@Component(modules = MockClockModule.class)
public interface TestComponent extends DemoComponent {
void inject(MainActivityTest mainActivityTest);
}
ApplicationComponent
is used when the app is run normally, and TestComponent
is used during tests. Both components injects into MainActivity
.
How does the MainActivity
know which component to use? It injects via the application, which stores the component.
private DemoComponent component = null;
@Override public void onCreate() {
super.onCreate();
if (component == null) {
component = DaggerDemoApplication_ApplicationComponent
.builder()
.clockModule(new ClockModule())
.build();
}
}
public void setComponent(DemoComponent component) {
this.component = component;
}
public DemoComponent component() {
return component;
}
We call setComponent()
in test, which runs before onCreate()
, so the TestComponent
is used. When the app is run normally, component
will be set to ApplicationComponent
in onCreate()
.
Exposing setComponent
in the application is not great because ideally the application should not have test-specific code. Another approach is to subclass the application in the androidTest
folder and load it during tests via a custom test runner. See blog.sqisland.com/2015/12/mock-application-in-espresso.html for more details.
Mockito
The app has a Clock
class which returns the current time.
public DateTime getNow() {
return new DateTime();
}
TestComponent
contains MockClockModule
, which provides Clock
as mocked by Mockito. This way MainActivityTest
can supply a pre-determined date during test.
Mockito.when(clock.getNow())
.thenReturn(new DateTime(2008, 9, 23, 0, 0, 0));
Since we have singleton modules, the same mocked Clock
is supplied to the app. With that, it will display the provided date instead of today's date:
onView(withId(R.id.date))
.check(matches(withText("2008-09-23")));
More
There is a lot more in the repo, including testing activity launch with intent and unit testing with JUnit. Please check it out:
https://github.com/chiuki/android-test-demo
Look at the setComponent branch to see the old code for injection using the setComponent
function.
Master uses mock application and custom test runner for injection.
Also, I am toying with the idea of writing a book on Espresso. Please take a look at the outline and fill in this form if you think I should write it!
Update: I have started publishing Espresso courses! https://gumroad.com/chiuki
Further reading: