Towards the end of last year I started working on a new project, Recommender. I found it interesting to compare the tools i selected in 2015 as against an earlier project such as measureme. when i started the new project with new tools i also wanted to try apply some structure to the code to help enable testing.

When projects are developed and tested and run all in the same environment then producing automated test suites are easier. For example I have worked on web apps that are built and tested in Visual Studio running on Windows and then the automated suite is run by TeamCity also and Windows and finally the app is deployed to IIS and Windows. Android apps are different, the target app must run on a handset of some description and apps are never developed on a handset. Many approaches to testing, such as Espresso and Calabash test the app on a handset or emulator, I have never found these to be particularly reliable or fast. An android project I worked on has around 100 Espresso tests and they only managed to run to completion less than 50% of the time, so for over 50% of the time I don't know if the build is broken or passing its tests. I think that there are simply too many moving parts and the toolset is to immature to enable reliable testing on a device or emulator.

Units tests on the desktop

To test in Recommender I decided that the tests would be run in the development/build environment using JUnit. JUnit is well established and reliable and running on the development environment meant that the tests could quickly be run by the developer before checking code in and also on the build server to ensure that the build was not broken. Testing like this does require some careful structuring of the code. I wanted to test the business logic and the building blocks of the model, which was fine, however testing in JUnit on the desktop means that I cannot access any Android OS API’s, so testing UI specific code was not going to be possible unless some kind of emulator was used.

The two main pieces that help with structuring the code were MVP and Dagger2. Using an MVP pattern enables me to separate out the UI code from the business logic.

complex activity

Typically the Activity contains all the UI code, which is kept deliberately simple expressions of intent. The Presenter contains all the business logic. The Presenter and all of its dependencies are injected by using Dagger2. This means that the business logic in the Presenter is separated from  the Android UI by the Activity and also from the nuts and bolts of the Android OS, such as HTTP transport and SQLite by Dagger2 injecting its dependencies.

Isolating logic using Dagger2 containers in Unit Tests

As I’ve already covered Recommender uses scoped Dagger2 containers to inject the dependencies of the Presenters.  The Application Module contains all the singleton stateless objects such as loggers, then there are activity modules that contains objects that have state and that a separate one is required for each activity. The Activity Module is spit into a Base and a derived as the Base on contains objects that can be used by multiple Presenters.

Scoped components

When testing the presenters it is possible to simply “new” up the presenter to test, passing in all of its dependencies, mocking as required. And this is a viable option. It can be a little cumbersome, for example some of the presenters have a lot of dependencies and also the dependencies themselves also have dependencies. It would be much better if I could use Dagger components and modules to create a presenter but with mocked objects when needed. It is possible to do this using Roboelectric. Roboelectric gives me some useful advantages when writing tests, these are useful but not essential.

  1. Emulation of Android OS classes. I try not to make much use of this as I do not really want to swap a dependency on one emulator for another however emulation of some of the basic constructs such as Intents and Parcels as well as access to resources have proved to be reliable.
  2. A test application instance. Roboelectric enables me to declare a further derived application that is launched for each test. This test application enables me to house the test versions of the Dagger2 modules and also override and suppress such things as database initialisation.

So for example the application component and module look like this in the real application

@Singleton // Constraints this component to one-per-application or unscoped bindings.
@Component(
  modules = ApplicationModule.class
)
public interface IApplicationComponent {
 // do not forget to list ALL classes that can ask to be injected
 // remove any that do NOT use this component
 void inject(OpenSourceLicensesActivity activity);

 // fragments without a scoped component just use the application component

 // we only need to put things in here that are exposed to sub graphs
 Context context();
 IEventBus provideEventBus();
}

@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;
 }

 @Provides
 @Singleton
 SQLiteOpenHelper provideOpenHelper(RecommenderDatabaseHelper helper) {
  return helper;
 }

 @Provides
 @Singleton
 IEventBus provideEventBus(RxEventBus eventBus) {
  return eventBus;
 }

}

The tests have a different application module which looks like this

@Singleton
@Component(
 modules = TestApplicationModule.class
)
public interface ITestApplicationComponent extends IApplicationComponent {
 // this should be empty
 // we dont have anything different in the test components from the production components
}

@Module
public class TestApplicationModule {
 private AndroidApplication mockApplication;
 private Logger mockLogger;
 private ILoggerFactory mockLoggerFactory;
 private SQLiteOpenHelper mockDbOpenHelper;
 private IEventBus mockEventBus;
 private boolean useRealEventBus;

 public TestApplicationModule(boolean useRealEventBus) {
  this.mockApplication = mock(AndroidApplication.class);
  when(mockApplication.getString(anyInt())).thenReturn("TEST RESOURCE STING");
  this.mockLoggerFactory = mock(ILoggerFactory.class);
  this.mockLogger = mock(Logger.class);
  when(mockLoggerFactory.getCurrentApplicationLogger()).thenReturn(mockLogger);
  this.mockDbOpenHelper = mock(SQLiteOpenHelper.class);
  this.mockEventBus = mock(IEventBus.class);
  this.useRealEventBus = useRealEventBus;
}

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

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

 @Provides
 @Singleton
 SQLiteOpenHelper provideOpenHelper() {
  return mockDbOpenHelper;
 }

 @Provides
 @Singleton
 IEventBus provideEventBus() {
  if (useRealEventBus) {
   // we are going to use the real event bus in the tests as its just easier
   // and there is very little behaviour in it
   // and anyway it needs testing as well
   return new RxEventBus();
  }
  return mockEventBus;
 }
}

The test component interface just extends the real component interface and does not add anything as the module will be a complete replacement for the real module, nothing added nothing taken away.

The mocks isolate the business logic under test from the Android OS. For example the SQLiteOpenHelper and the ILoggerFactory are pure mocks, the IEventBus can be optionally a real instance or a mock. The mocking framework I use is Mockito, it is stable and very flexible and a great partner for JUnit.

The same pattern is repeated for the BaseActivityModule, however I do not repeat the pattern again for the final layer as the only thing the activity module contains is the activity or fragment presenter and that is the object I want to test so I can use the real module from the application.

The TestApplication instance

The Roboelectric test runner will launch a test application if one is present. To create a test application all that is needed is to create a class that derives from the main application, is in that same namespace / package as the main application and implements the TestLifecycleApplication interface. This is my test application

package net.derekwilson.recommender;

import net.derekwilson.recommender.ioc.IApplicationComponent;
import net.derekwilson.recommender.tests.ioc.DaggerITestApplicationComponent;
import net.derekwilson.recommender.tests.ioc.ITestApplicationComponent;
import net.derekwilson.recommender.tests.ioc.TestApplicationModule;

import org.robolectric.RuntimeEnvironment;
import org.robolectric.TestLifecycleApplication;

import java.lang.reflect.Method;

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

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

 protected ITestApplicationComponent component;

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

 public ITestApplicationComponent getTestApplicationComponent(boolean forceRebuild, boolean useRealEventBus) {
  if (component == null || forceRebuild) {
   this.component = DaggerITestApplicationComponent.builder()
     .testApplicationModule(new TestApplicationModule(useRealEventBus))
     .build();
  }
  return component;
 }

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

 @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) {
 }
}

As the TestAndroidAppliaction extends the main AndoirdApplication it can be used where we would usually use the real application.

Putting it all together

So to test an individual call in a presenter I use this pattern.

@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class)
public class UpdateSearchTests extends PresenterTestsSetup {
 String testSearchString = "TEST";

 @Before
 public void setUp() {
  setupPresenter(true, true);
 }

 @Test
 public void we_can_set_a_search_filter() {
  // arrange
  presenter.onCreate();

  // act
  presenter.updateSearchFilter(testSearchString);

  // assert
  ArgumentCaptor argument = ArgumentCaptor.forClass(FilterCollection.class);
  verify(mockView, times(1)).applyFilter(argument.capture());
  assertThat("incorrect search filter", argument.getValue().getSearchFilter(), is(testSearchString));
  assertThat("incorrect search type", argument.getValue().getFilterType(), is(FilterCollection.FilterType.Search));
 }
}

The @RunWIth and @Config direct Roboelectric how to launch the test application.

This test checks that when the presenter method updateSearchFilter is called it will in turn call applyFilter on the view with the correctly formatted parameters. The FilterCollection parameter should contain the search string. To verify this happens I create a mockView, which is a mock that has the same signature that the Activity UI exposes. I can then use Mockito’s ArgumentCaptor to inspect the parameters that were passed to the view.

The SetupPresenter method is supplied by the base class like so

public class PresenterTestsSetup extends BaseRoboelectricTests {

 // mocks
 protected Activity mockActivity = mock(Activity.class);
 protected IMainActivityView mockView = mock(IMainActivityView.class);
 protected IRecommendationRepository mockRepository = mock(IRecommendationRepository.class);

 // will use a concrete object until we have to use a mock
 protected TestRecommendationRepository realRepository = new TestRecommendationRepository();

 protected IMainComponent component = null;
 protected IEventBus eventBus = null;

 // object under test
 protected IMainActivityPresenter presenter;

 protected IMainComponent getComponent(IRecommendationRepository repository) {
  if (component == null) {
   ITestApplicationComponent applicationComponent = getTestApplicationComponent(true, true);
   eventBus = applicationComponent.provideEventBus();
   component = DaggerITestMainComponent.builder()
     .iTestApplicationComponent(applicationComponent)
     .testBaseActivityModule(new TestBaseActivityModule(mockActivity, repository))
     .mainModule(new MainModule())
     .build();
  }
  return component;
 }

 protected void setupPresenter(boolean bind, boolean useRealRepository) {
  if (useRealRepository) {
   presenter = getComponent(realRepository).getPresenter();
  }
  else {
   presenter = getComponent(mockRepository).getPresenter();
  }
  if (bind) {
   presenter.bindView(mockView);
  }
 }
}

public class BaseRoboelectricTests extends BaseJunitTests {
 protected TestAndroidApplication getApplication() {
  return ((TestAndroidApplication) RuntimeEnvironment.application);
 }

 protected IApplicationComponent getApplicationComponent() {
  return getApplication().getApplicationComponent();
 }

 protected ITestApplicationComponent getTestApplicationComponent(boolean forceRebuild, boolean useRealEventBus) {
  return getApplication().getTestApplicationComponent(forceRebuild, useRealEventBus);
 }
}

The getComponent call creates a Dagger component that has the TestModules in it, that are populated with mocks. We also make use of the TestApplication module in the TestApplication supplied by Roboelectric and then the call to getPresenter will provide the real presenter to test with all of its dependencies supplied from the TestModules.

Does it work?

Yes. I have used this mechanism on a number of projects and the test are fast and reliable, very well suited to a developer machine and a build server. As an example one project has 250 tests that run in 13 seconds and have run multiple times per day for months without a single failed run. That isn't to say that the build has never broken rather it is to say that the status of the build is never in doubt.

If you want to see the complete source code then its available in Bitbucket.