Improve the Testability of Your Android App

The most effective way to improve the testability of your Android app is through a technique such as dependency injection. Dependency injection on Android is a notoriously difficult problem to tackle. The problem stems from the fact that Android manages the creation and destruction of all activity instances, thereby making it difficult for us to provide dependencies. The diagram below, taken from the Android developer docs, illustrates the activity lifecycle:

Android activity lifecycle

So why is this an issue? When building testable apps, we want to be able to inject dependencies into our activities. This allows us to swap out our "real" objects with testable mocks. However, we can't do this since we don't have control over the creation of our activities. Unfortunately, this tends to promote a design involving heavy use of static singletons. Let's take a look at an example of a typical Android activity class.

Note: I'm not going to go into detail over what dependency injection and inversion of control (IoC) is. It has become a fairly common design pattern in the past few years, and there are plenty of other resources that can explain it far better than I can.

Basic Activity Example

/**
 * A simple activity that makes a web service call when a button is clicked.
 */
public class MainActivity extends Activity {
	private WebServiceClient client;

	@Override
	public void onCreate(final Bundle savedInstanceState) {
		client = WebServiceClient.getInstance();

		Button btn = (Button) findViewById(R.id.btn);
		btn.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(final View v) {
				client.doSomething();
			}
		});
	}
}

This makes sense at a first glance, but now say we want to test our activity. How do we do this without making real web service calls? We can create an integration test that makes calls to our server, but those are slow, less reliable, and require a separate testing environment.

Also notice that there is no straightforward way of providing objects to the onCreate() method. We cannot add any additional parameters because it is an overridden method defined by the base Activity class. We also cannot create a constructor because the Android OS only knows how to call the default empty constructor. So how do we fix this?

Using The Service Locator Pattern

The first option is to use the service locator pattern. If you aren't familiar with this pattern, it's basically a way for our activity to grab the services (or dependencies) it needs.

The basic idea behind a service locator is to have an object that knows how to get hold of all of the services that an application might need.

Inversion of Control Containers and the Dependency Injection Pattern by Martin Fowler

The following is an example of how this might work in the context of an Android app.

Android Service Locator

public class MainActivity extends Activity {
	private WebServiceClient client;

	@Override
	public void onCreate(final Bundle savedInstanceState) {
		client = ServiceLocator.getServiceLocator().getWebServiceClient();

		Button btn = (Button) findViewById(R.id.btn);
		btn.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(final View v) {
				client.doSomething();
			}
		});
	}
}
public class ServiceLocator {
	private static ServiceLocator instance;
	private IWebServiceClient webServiceClient;

	public static void setServiceLocator(ServiceLocator serviceLocator) {
		this.instance = serviceLocator;
	}

	public static ServiceLocator getServiceLocator() {
		return this.serviceLocator;
	}

	public ServiceLocator(IWebServiceClient webServiceClient) {
		this.webServiceClient = webServiceClient;
	}

	public IWebServiceClient getWebServiceClient() {
		return this.webServiceClient;
	}
}

Now we can provide our ServiceLocator with a mock WebServiceClient when writing tests.

Testing With Service Locator

public class MainActivityTests {

	@Test
	public void testClickingButtonDoesSomethingWithWebService() {
		IWebServiceClient mockWebService = createMock(WebServiceClient.class);
		ws.doSomething();
		expectLastCall();
		replay(mockWebService);

		ServiceLocator sl = new ServiceLocator(mockWebService);

		verify(mockWebService);
	}
}

Our code is now much better. We can easily test logic that makes web service calls, our dependencies are clearly defined through ServiceLocator, and we've reduced the coupling between classes.

Using Dagger

The nice part about using the service locator pattern is that it can work nicely with regular dependency injection. Dagger is a lightweight IoC framework that was designed with Android in mind. Dagger can automatically handle our dependencies, alleviating a lot of the effort.

The standard way of using an IoC framework, like Dagger, is to resolve all of your dependencies at startup. However, on Android this isn't possible because we cannot instantiate activities ourself. Instead, we can use Dagger's ObjectGraph as a type of service locator.

Dagger Example

public class MainActivity extends Activity {

	@Inject
	IWebServiceClient client;

	@Override
	public void onCreate(final Bundle savedInstanceState) {
		MyApp app = (MyApp) getApplication();
		app.getObjectGraph().inject(this);

		Button btn = (Button) findViewById(R.id.btn);
		btn.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(final View v) {
				client.doSomething();
			}
		});
	}
}
@Module(entryPoints = MainActivity.class)
public class DefaultModule {
	@Provides IWebServiceClient provideWebServiceClient() {
		return new WebServiceClient();
	}
}
public class MyApp extends Application {
	private ObjectGraph objectGraph;

	@Override
	public void onCreate() {
		super.onCreate();
		this.objectGraph = ObjectGraph.create(new DefaultModule());
	}

	public ObjectGraph getObjectGraph() {
		return this.objectGraph;
	}
}

So there we have it. I won't go into the details of how to use Dagger, but if you've used an IoC framework before, the syntax is fairly self-explanatory. Here, we get an instance of Dagger's object graph and use it to inject dependencies into our MainActivity object. Our Dagger ObjectGraph instance is essentially acting as a service locator.

Advantages

Using IoC provides a few advantages over our manual service locator:

Summary

As mobile devices become more powerful, the apps we create become more complex. It's important to take the time to ensure your Android apps are designed in a modular, testable way. It is generally agreed that a lack of automated testing is no longer acceptable for desktop and web apps, and mobile shouldn't be any exception.