Sample Android application using Dagger 2, Robolectric, and Mockito
One of the most compelling reasons to use dependency injection to be able to inject mock versions of dependencies in your unit test environment. This sample application shows you how to do that using Dagger 2 with Robolectric and Mockito.
This application has two Dagger modules: AndroidModule
and CommonModule
. These modules are used to construct an ApplicationComponent
in MyApplication
.
MyApplication.java
public class MyApplication extends Application {
@Singleton @Component(modules = { AndroidModule.class, CommonModule.class })
public interface ApplicationComponent {
void inject(MainActivity mainActivity);
}
private ApplicationComponent component;
@Override public void onCreate() {
super.onCreate();
component = DaggerMyApplication_ApplicationComponent.builder()
.androidModule(new AndroidModule(this))
.commonModule(new CommonModule())
.build();
}
public ApplicationComponent component() {
return component;
}
}
AndroidModule.java
@Module
public class AndroidModule {
private final MyApplication application;
public AndroidModule(MyApplication application) {
this.application = application;
}
@Provides @Singleton StringFactory provideStringFactory() {
return new StringFactory(application);
}
}
CommonModule.java
@Module
public class CommonModule {
@Provides @Singleton NumberFactory provideNumberFactory() {
return new NumberFactory();
}
}
The ApplicationComponent
is then used to inject dependencies like StringFactory
and NumberFactory
into MainActivity
.
MainActivity.java
public class MainActivity extends ActionBarActivity {
@Inject StringFactory stringFactory;
@Inject NumberFactory numberFactory;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((MyApplication) getApplication()).component().inject(this);
((TextView) findViewById(R.id.greeting)).setText(stringFactory.makeText());
((TextView) findViewById(R.id.number)).setText("The magic number is "
+ numberFactory.getMagicNumber() + ".");
}
}
However in the unit tests we may want to substitute a mock object at runtime for one or more injected dependencies. In this example we want to replace StringFactory
with a mock generated by Mockito. We achieve this by replacing AndroidModule
with TestAndroidModule
when building the component in TestMyApplication
.
TestMyApplication.java
public class TestMyApplication extends MyApplication {
@Singleton @Component(modules = { TestAndroidModule.class, CommonModule.class })
public interface TestApplicationComponent extends ApplicationComponent {
void inject(MainActivity mainActivity);
}
private TestApplicationComponent component;
@Override public void onCreate() {
super.onCreate();
component = DaggerTestMyApplication_TestApplicationComponent.builder()
.testAndroidModule(new TestAndroidModule(this))
.commonModule(new CommonModule())
.build();
}
@Override public ApplicationComponent component() {
return component;
}
}
TestAndroidModule.java
@Module
public class TestAndroidModule {
private final TestMyApplication application;
public TestAndroidModule(TestMyApplication application) {
this.application = application;
}
@Provides @Singleton StringFactory provideStringFactory() {
StringFactory mockStringFactory = Mockito.mock(StringFactory.class);
Mockito.when(mockStringFactory.makeText()).thenReturn("Fake greeting");
return mockStringFactory;
}
}
Now when we run our tests a mock StringFactory
will be injected into MainActivity
instead of the real object. Note that CommonModule
is not swapped out in the test environment so NumberFactory
will behave the same in the app and tests.
MainActivityTest.java
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
public class MainActivityTest {
private MainActivity mainActivity;
@Before public void setUp() throws Exception {
mainActivity = Robolectric.setupActivity(MainActivity.class);
}
@Test public void shouldInjectMockStringFactory() throws Exception {
TextView greeting = (TextView) mainActivity.findViewById(R.id.greeting);
assertEquals("Fake greeting", greeting.getText());
}
@Test public void shouldInjectMagicNumber() throws Exception {
TextView number = (TextView) mainActivity.findViewById(R.id.number);
assertEquals("The magic number is 4.", number.getText());
}
}