/dagger-2-testing-demo

Sample Android application using Dagger 2, Robolectric, and Mockito

Primary LanguageJava

Dagger 2 Testing Demo

Sample Android application using Dagger 2, Robolectric, and Mockito

Overview

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());
    }
}