I have started a new project using AndroidStudio and Dagger. Using inversion of control is all well and good but its only there to help with isolation code and one of the main reasons for doing this is to enable objects to be more easily tested.

In previous projects I have written pure junit tests. These worked well enough however it does mean that I am only able to test a relatively small number of objects.

For this new project I wanted to extent my testing into as much of the code as I could. This is where Dagger2 and Roboelectric come in.

For the production app I created an IoC container like this.

@Module
public class ApplicationModule {
 private final AndroidApplication application;

 public ApplicationModule(AndroidApplication application) {
  this.application = application;
 }

 @Provides
 @Singleton
 Context provideApplicationContext() {
  return this.application;
 }

 @Provides
 @Singleton
 ILoggerFactory provideLogger(SlfLoggerFactory loggerFactory) {
  return loggerFactory;
 }
}

And the container is initialised by the application like this

public class AndroidApplication extends Application {
 private ApplicationComponent applicationComponent;

 @Override
 public void onCreate() {
  super.onCreate();
  this.applicationComponent = initialiseInjector();
 }

 protected ApplicationComponent initialiseInjector() {
  return DaggerApplicationComponent.builder()
    .applicationModule(new ApplicationModule(this))
    .build();
 }

 public ApplicationComponent getApplicationComponent() {
  return this.applicationComponent;
 }

Next I created a test package, Android Studio 1.3 has got pretty good support for unit testing, and I needed to use 1.3 or later to get the support for Dagger code generation in the test project. I created the package in the folder called test in the AndroidStudio project like this.

src layout

I am using the Roboelectric v3 test runner which will automatically use the test application if it is created in the same namespace as the real application. The test application looks like this

package net.derekwilson.recommender;

/**
 * this is the application created by Roboelectric
 */
public class TestAndroidApplication extends AndroidApplication implements TestLifecycleApplication {

 // these should move to the module when we can move it into the test package
 // maybe AndroidStudio 1.4
 private Logger mockLogger;
 private ILoggerFactory mockLoggerFactory;

 @Override
 public void onCreate() {
  System.out.print("Test application created v" + getVersionName(this) + "\n");
  super.onCreate();
 }

 protected TestApplicationComponent component;

 protected AndroidApplication getApplication() {
  return ((AndroidApplication) RuntimeEnvironment.application);
 }

 protected ApplicationComponent getTestApplicationComponent() {
  if (component == null) {
   this.mockLoggerFactory = mock(ILoggerFactory.class);
   this.mockLogger = mock(Logger.class);
   when(mockLoggerFactory.getCurrentApplicationLogger()).thenReturn(mockLogger);
   this.component = DaggerTestApplicationComponent.builder()
     .testApplicationModule(
        new TestApplicationModule(
           getApplication(),
           mockLoggerFactory))
     .build();
  }
  return (ApplicationComponent) component;
 }

 @Override
 protected ApplicationComponent initialiseInjector() {
  System.out.print("Setting the application mocked component\n");
  return getTestApplicationComponent();
 }

 @Override
 protected void initialiseDatabase() {
  // do not call super as we do not want to initialise the DB
 }

 @Override
 public void beforeTest(Method method) {
 }

 @Override
 public void prepareTest(Object test) {
 }

 @Override
 public void afterTest(Method method) {
 }
}

The logger, as well as all the other components in the container, are mocks rather than real objects. The test IoC container is setup like this

@Singleton
@Component(modules = TestApplicationModule.class)
public interface TestApplicationComponent extends ApplicationComponent {
}

@Module
public class TestApplicationModule {
 private final AndroidApplication application;
 private final ILoggerFactory mockLogger;

 public TestApplicationModule(
  AndroidApplication application, 
  ILoggerFactory mockLogger) {

  this.application = application;
  // we would not need to do this if we could create the mocks here
  // however we cannot do that unless we can move this class to the junit package
  this.mockLogger = mockLogger;
 }

 @Provides
 @Singleton
 Context provideApplicationContext() {
  return this.application;
 }

 @Provides
 @Singleton
 ILoggerFactory provideLogger() {
  return mockLogger;
 }
}

The main trick here is that the @Component interface inherits from the ApplicationComponent, it does not add anything to the interface as the tests use the component in the same manner as the main application. It does however specify a TestApplicationModule in the modules list. This module is the module that is used to initialise the main applications component by overriding the initialiseInjector method in the test application.

I also override the initialiseDatabase method from the main application this is a method that I wrote to setup the SQLite database, in the tests we just do nothing as we also use the test IoC container to present a mock database to the tests, this makes the tests run much faster.


Then we write junit tests like this, the scoped component is built using the test application component returned from getApplicationComponent which will ensure that mocks are injected for all objects obtained from the container.

@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class)
public class HasBeenEditedTests extends PresenterTestsSetup {

 // mocks
 Activity mockActivity = mock(Activity.class);
 IEditRecommendationView mockView = mock(IEditRecommendationView.class);

 // will use a concrete object until we have to use a mock
 Recommendation recommendationPassedToActivity;
 Recommendation recommendationFromUi;

 // object under test
 IEditRecommendationPresenter presenter;

 private EditRecommendationComponent getComponent() {
  return DaggerEditRecommendationComponent.builder()
    .applicationComponent(getApplicationComponent())
    .baseActivityModule(new BaseActivityModule(mockActivity))
    .editRecommendationModule(new EditRecommendationModule())
    .build();
 }

 @Before
 public void setUp() {
  // setup required behaviour
  when(mockView.getRecommendation()).thenAnswer(new Answer() {
    @Override
    public Recommendation answer(InvocationOnMock invocation) throws Throwable {
      return recommendationPassedToActivity;
    }
  });
  when(mockView.getRecommendationFromUi()).thenAnswer(new Answer() 
    @Override
    public Recommendation answer(InvocationOnMock invocation) throws Throwable {
      return recommendationFromUi;
    }
  });

  // create object under test
  presenter = getComponent().getPresenter();
  presenter.bindView(mockView);
 }

 @Test
 public void create_empty_recommendation_is_not_dirty() {
  // arrange
  recommendationPassedToActivity = null;
  recommendationFromUi = setupRecommendation("","","","","");

  // act
  boolean edited = presenter.hasBeenEdited();

 // assert
 assertThat(edited, is(false));
}

For this mechanism to work I needed to ensure that I has the latest version of apt specified (v1.7) and also gradle 1.3 in my gradle file. The top level gradle file looks like this

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.3.0'
        // This plugin helps Android Studio find Dagger's generated classes
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.7'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

And then the project gradle like this, we need to specify testApt to get the Dagger code generated for the tests. Also note that I am compiling using SDK v23 however I need to target SDK v21 for Roboelectric to work.

apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'

android {
 compileSdkVersion 23
 buildToolsVersion "23.0.1"

 defaultConfig {
  applicationId "net.derekwilson.recommender"
  minSdkVersion 15
  targetSdkVersion 21
  versionCode 11
  versionName "0.39"
 }

dependencies {
...

 // tests
 testApt 'com.google.dagger:dagger-compiler:2.0.1'
 testCompile 'com.google.dagger:dagger:2.0.1'
 testCompile "org.robolectric:robolectric:3.0"
 testCompile "junit:junit:4.10"
 testCompile "org.mockito:mockito-core:1.+"
}

The last piece of the puzzle is to create a configuration to run the tests like this

runner config

And finally to select the test artefact to be unit tests rather than Android Tests.

build variant

Then just select run and the tests will run in AndroidStudio or from the command line using gradle.