Serious unit testing on Android
June 15, 2012 | 4 min ReadIt is not a secret that we are big fans of Mockito at EclipseSource. Our Java mocking framework of choice has helped us in many projects to stay on the test driven development path. Luckily we are also able to apply these same principles when developing Java code on Android.
Although Mockito is great for mocking Java classes, it would sometimes be very cumbersome to mock entire parts of the Android UI framework. This is the place where we resort to another testing framework called Robolectric, which can be easily combined with Mockito to provide stub implementations of nearly all Android classes.
Robolectric provides concrete implementations for Android classes in the form of shadow objects. When running your test code you don’t execute the Android code but run against the shadow classes. Because you are executing regular Javacode, you don’t have to run the tests on an Android device but can execute the tests in a regular Java VM.
A Robolectric shadow class allows you to easily make assumptions about its current state because it can expose more of its internals than an Android class would. Here is a quick example to assert if an OnTouchListener has been attached to a View:
@Test
public void shouldHaveOnTouchListener() {
View view = new View(new Activity());
OnTouchListener listener = Mockito.mock(OnTouchListener.class);
view.setOnTouchListener();
ShadowView shadowView = (ShadowView)Robolectric.shadowOf(view);
assertEquals(listener, shadowView.getOnTouchListener());
}
Don´t forget to put the @RunWith(RobolectricTestRunner.class) annotation in the test class. It is also worth noting that the new Activity() we are creating is, in fact, a ShadowActivity.
The assertion could not have been done without the ShadowView since Android does not have a getter for the OnTouchListener. Of course we could have mocked the View object with Mockito but that is not always possible, e.g. when the view has been inflated from a layout or has been created in some other place.
[ Want to develop for Android and iOS from a single Java codebase? Try Tabris: download the 30-day trial or check out Tabris technology overview from our blog. ]
Sometimes even Robolectric falls short on the elements you can assert. In that case you can easily extend an existing shadow object to provide implementation details that suit your test case. Let’s say we want to assert that TextView.setIncludeFontPadding() has been called. Android does not provide a getter for that property and Robolectric does not implement the method so the call is simply swallowed. This is where we implement our own Shadow class:
@Implements(TextView.class)
public class CustomShadowTextView extends ShadowTextView {
private boolean includepad = true;
@Implementation
public void setIncludeFontPadding( boolean includepad ) {
this.includepad = includepad;
}
public boolean getIncludeFontPadding() {
return includepad;
}
}
Once we have defined the shadow object we can use it in our test case. Whenever our code tries to instantiate a TextView it is now providing an implementation of CustomShadowTextView. The following snippet shows how to set our custom shadow view:
@Test
public void shouldIncludeFontPadding() {
Robolectric.bindShadowClass(CustomShadowTextView.class);
TextView view = new TextView(new Activity());
view.setIncludeFontPadding(true);
CustomShadowTextView shadowView = (CustomShadowTextView)Robolectric.shadowOf(view);
assertTrue(shadowView.getIncludeFontPadding());
}
There are some shortcomings with the way Robolectric is able to reference Android resources. Although it is generally possible to find all kinds of images, Robolectric does have problems when referencing theme and style attributes. In that case we often resort to testing specific custom shadow objects that hardwire the desired behavior. In addition, some Android classes do not have a corresponding shadow class. A good example is the Typeface class. In that case you have to compile Robolectric manually and add a Robolectric.shadowOf(typeface) method.
In general we find the features provided by a combination of Mockito and Robolectric to be more than sufficient and whole-heartedly recommend the setup to anyone serious about unit testing. The speed at which you can write and execute tests is an important factor that will allow you to iterate on your test code quickly and in a meaningful way.