Advanced Android testing with RoboGuice and Robolectric by Moritz Post in EclipseSource News

I’ve been using the Android framework RoboGuice 2.0 a lot lately. RoboGuice is an extension for the Google Guice dependency injection framework that adds several Android specific mechanisms to easily inject views, services or custom objects. Coming from an OSGi background, it is interesting to see how much deeper Guice is able to be interwoven into the Java code when your entire project is composed of injected classes. OSGi injects components on a much more abstract service level whereas Guice encourages injecting every class from a widget to a util class.

Having such a loosely coupled project poses a new set of problems for testing the system. In theory it should be super simple to create tests for a Guice-based application but in practice several challenges have to be overcome when injecting test relevant classes. This blog post will walk you through a minimal project that uses RoboGuice accompanied by a test project that uses Robolectric and Mockito for test execution.

Creating the projects

To get started with RoboGuice we’ll create a simple project that is able to convert distances from feet to meters. Check out the screenshot below to see the UI in action. You can download the sample project and test project from here (the android.jar is omitted and should be linked in the test project).

The first thing to do is create the main Android project and add the dependencies. To use RoboGuice you will need RoboGuice, Guice, javax.inject and the jsr305 library. You can find most of these libraries on the downloads page of the RoboGuice website (javax.inject.jar is in the guice-3.0 archives) and you can find the jsr305 jar on the Guice website.

The next step is to create a regular Java project that will act as the test project. When running tests with Robolectric you do not run the tests in the context of a real Android device, but create regular Java JUnit tests. For the test project we need to add dependencies for the robolectric-all.jar and Mockito. We also need to add (or reference) an android.jar in order to have an Android version to run the test against. Check out the screenshot to the right to see the project setup in Eclipse.

To run RoboGuice tests, we place the tests in a test folder inside the main com.eclipsesource.metric project and link to that folder from the test project. To create a relative link to that folder, you can place this handy little snippet in the .project file of the test project:

test 2 PARENT-1-PROJECT_LOC/com.eclipsesource.metric/test

To run the tests, which are now available in the test project via the link, consult the Robolectric docs that go into detail on how to create an Eclipse test runner. It’s pretty simple actually. The sample projects even contain a convenient preconfigured launch config.

To help you with all the project setup stuff you could also try to use Android Bootstrap.

Wiring the app logic with RoboGuice

We can now create our little converter application. Our main Activity will declare the UI and wire the button to perform the conversion:

public class MetricActivity extends RoboActivity { private @InjectView(R.id.convert_button) ImageButton convertButton; private @Inject ConvertFeetToMeterListener listener; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); convertButton.setOnClickListener(listener); } }

This code looks really simple and in fact, it is. All we had to do was to set our content view in onCreate() and attach our button listener. The wiring of the listener class happens in the super.onCreate() call, while the button is injected at the moment we set the content view. RoboGuice is then able to find the button via its ID in the layout previously passed in setContentView.

Let’s have a look at the implementation of the listener:

public class ConvertFeetToMeterListener implements OnClickListener { private @Inject ConversionService conversionService; private @InjectView(R.id.feet_edit_text) EditText feetEditText; private @InjectView(R.id.result_text) TextView resultText; @Override public void onClick(View v) { try { float feet = Float.parseFloat(feetEditText.getText().toString()); float meter = conversionService.convertFeetToMeter(feet); resultText.setText(feet + " feet is " + meter + " meter"); } catch (NumberFormatException e) { // handle error } } }

In the listener we inject the view objects from the UI to get the input value and to present the output value. Instead of performing the calculation in the listener, we make things a little more interesting and use the custom ConversionService class to perform the conversion. Think of the service as something you need for your app, for example a service to make REST calls, monitor system preferences, etc. In general it will perform the requested task but during the testing scenario we are not necessarily interested in how the work is executed. For the sake of completeness here is the code for the ConversionService:

public class ConversionService { private float footInMeter; public ConversionService(float footInMeter) { this.footInMeter = footInMeter; } public float convertFeetToMeter(float feet) { return feet * footInMeter; } }

You will notice that we pass the conversion factor in the constructor instead of keeping it as a constant in the service. We do so in order to simulate a very common application scenario, where your service depends on external parameters. From an injection standpoint this makes it hard to simply instantiate the object via RoboGuice. We could add the @Inject annotation on the constructor but Guice wouldn’t know how to populate the float param. Only if the param was a type Guice already knew about (like an Activity.class) could the instantiation be performed by Guice. In this case we have to tell Guice how to instantiate the ConversionService class. We create a custom com.google.inject.Provider that passes our argument in the constructor:

public class ConversionServiceProvider implements Provider { private static final float FOOT_IN_METER = 0.3048f; @Override public ConversionService get() { return new ConversionService(0.3048f); } }

Now that we have the Provider implementation we need to tell Guice how to use it. We have to create a com.google.inject.Module that binds classes to certain components such as providers, concrete objects or type listeners:

public class MetricGuiceModule extends AbstractModule { @Override protected void configure() { bind(ConversionService.class).toProvider(ConversionServiceProvider.class).in(Singleton.class); } }

This module bundles all of our application specific bindings. Often RoboGuice is not detailed enough so you will have to add custom bindings for Android components as well. In our MetricGuiceModule we bind the ConversionService to our provider and declare the created instance to be an application-wide singleton. The last piece in the puzzle is to tell RoboGuice to use our custom bindings. Luckily RoboGuice provides a very Android-typical approach to add custom modules. We create the xml file res/values/roboguice.xml (the file name can be anything but the path has to be same) with the following content:

com.eclipsesource.metric.roboguice.MetricGuiceModule

Your application should now run properly and be able to convert from feet to meters.

Creating tests for the RoboGuice project

Testing the project should now be straightforward. After all, we have decoupled the components nicely and have a clear separation between UI and application logic. But behold… there are still some gotchas. Fortunately we have Robolectric to take care of UI elements creation. We also want to create mock objects for our services. So, we need to combine the injection mechanism of RoboGuice, the view creation from Robolectric and the mock creation from Mockito. Let’s start with the tests for the MetricActivity class:

@RunWith(RobolectricTestRunner.class) public class MetricActivity_Test { @Mock ConvertFeetToMeterListener listener; @Before public void setUp() { MockitoAnnotations.initMocks(this); TestGuiceModule module = new TestGuiceModule(); module.addBinding(ConvertFeetToMeterListener.class, listener); TestGuiceModule.setUp(this, module); } @After public void tearDown() { TestGuiceModule.tearDown(); } @Test public void onCreateShouldAttachConversionListenerToConvertButton() { MetricActivity activity = new MetricActivity(); activity.onCreate(null); ShadowView button = Robolectric.shadowOf(activity.findViewById(R.id.convert_button)); assertEquals(listener, button.getOnClickListener()); } }

Our test class has to use the RobolectricTestRunner, otherwise it cannot create Android UI elements. Looking at our test method we see that we only create a new activity, call onCreate() and assert something on a shadow object generated by Robolectric. If we hadn’t used Robolectric we would have had a hard time getting access to the listener because Android has no getter for it.

[ Want to build Android and iOS apps from a single Java codebase? Try Tabris: download the 30-day trial or check out Tabris technology overview from our blog. ]

The test is simple but it only works because we initiate a couple of things in the setUp method. First of all, we use MockitoAnnotations.initMocks(this) to create mocks for all objects annotated with @Mock. Secondly, we create a new TestGuiceModule. This module is similar to the MetricGuiceModule we created before, allowing us to bind objects for certain types. Next we add bindings to the module and finally call TestGuiceModule.setUp (this module), which performs the module initialization and binds the the objects. The use of the static methods of TestGuiceModule is one possible approach. (The code passages can be put elsewhere if desired.) Our last step is to do some clean up in tearDown(). Here is the code for the TestGuiceModule so far:

public class TestGuiceModule extends AbstractModule { private HashMap , Object> bindings; public TestGuiceModule() { bindings = new HashMap , Object>(); } @Override @SuppressWarnings("unchecked") protected void configure() { bind(Activity.class).toProvider(ActivityProvider.class).in(ContextSingleton.class); Set , Object>> entries = bindings.entrySet(); for (Entry , Object> entry : entries) { bind((Class ) entry.getKey()).toInstance(entry.getValue()); } } public void addBinding(Class> type, Object object) { bindings.put(type, object); } public static void setUp(Object testObject, TestGuiceModule module) { Module roboGuiceModule = RoboGuice.newDefaultRoboModule(Robolectric.application); Module productionModule = Modules.override(roboGuiceModule ).with(new MetricGuiceModule()); Module testModule = Modules.override(productionModule).with(module); RoboGuice.setBaseApplicationInjector(Robolectric.application, RoboGuice.DEFAULT_STAGE, testModule); RoboInjector injector = RoboGuice.getInjector(Robolectric.application); injector.injectMembers(testObject); } public static void tearDown() { RoboGuice.util.reset(); Application app = Robolectric.application; DefaultRoboModule defaultModule = RoboGuice.newDefaultRoboModule(app); RoboGuice.setBaseApplicationInjector(app, RoboGuice.DEFAULT_STAGE, defaultModule); } }

The TestGuiceModule collects the active bindings and once Robolectric calls the configure() method we bind each object to its type. Note that we also bind the Activity.class to our custom ActivityProvider. This step is necessary since RoboGuice is not aware of the current Activity context is in which the app runs. Only when we create an Activity manually would RoboGuice have a context to work in.

The majority of the work happens in the setUp method of the TestGuiceModule. Here we tell RoboGuice to use our custom test module. Since we have also created the MetricGuiceModule and want to preserve these bindings during test execution we have to create a module cascade. We overwrite the bindings from the default RoboGuice module with the ones from the MetricGuiceModule and overwrite these bindings with the TestGuiceModule bindings. Overwriting means to replace preexisting bindings with the ones in the new module but keeping the remaining intact. The relations look like this:

DefaultRoboModule < MetricGuiceModule < TestGuiceModule

To see all the bindings Robolectric predefines for us, check out the class roboguice.config.DefaultRoboModule. Finally, we use the tearDown() method to revert our test specific bindings and return to the Robolectric default bindings.

We have now written a test for our MetricActivty. Next, we have to test the listener that interacts with the UI and the ConversionService. Here is the test class for the ConvertFeetToMeterListener:

@RunWith(RobolectricTestRunner.class) public class ConvertFeetToMeterListener_Test { private @Inject ConvertFeetToMeterListener listener; private @Mock ConversionService conversionService; private @Mock TextView resultText; private EditText feetEditText; @Before public void setUp() { MockitoAnnotations.initMocks(this); Activity anyActivity = mock(Activity.class); feetEditText = new EditText(anyActivity); TestGuiceModule module = new TestGuiceModule(); module.addBinding(ConversionService.class, conversionService); module.addViewBinding(R.id.feet_edit_text, feetEditText); module.addViewBinding(R.id.result_text, resultText); TestGuiceModule.setUp(this, module); } @After public void tearDown() { TestGuiceModule.tearDown(); } @Test public void onClickShouldConvertFeedAndSetResult() { feetEditText.setText("3"); when(conversionService.convertFeetToMeter(3f)).thenReturn(0.9144f); listener.onClick(mock(View.class)); verify(resultText).setText("3.0 feet is 0.9144 meter"); } }

The test method itself should be pretty self-explanatory. We set the value of the input field to “3”, init our mock with a predefined result, call the listener and verify that the result has been set on the output mock. The interesting part of this test is the combination of UI elements in the form of a “real” EditText field and a mock for the output value. Any of these two could be a mock or a real object. In fact, the only reason we do not mock everything is that we can demonstrate another important topic and that is intercepting Robolectric-specific annotations. In the ConvertFeetToMeterListener we inject the UI elements via @InjectView(<viewId>). In our test code we need to overwrite the View injection and pass in our own implementation of the View (be it a mock or a “real” View). Therefore we extend the TestGuiceModule with an addViewBinding that simply populates a map with the key value pair. Next we have to extend the TestGuiceModule.configure() method to apply our view bindings:

@Override @SuppressWarnings("unchecked") protected void configure() { .. bindListener(Matchers.any(), new ViewTypeListener()); } .. private final class ViewTypeListener implements TypeListener { @Override public void hear(TypeLiteral typeLiteral, TypeEncounter typeEncounter) { for (Class> c = typeLiteral.getRawType(); c != Object.class; c = c.getSuperclass()) { for (Field field : c.getDeclaredFields()) { if (field.isAnnotationPresent(InjectView.class)) { typeEncounter.register(new TestViewMembersInjector(viewBindingMap, field)); } } } } }

Calling bindListener() with a Matcher that accepts every possible binding attempt allows us to intercept bindings with our custom ViewTypeListener. In that listener we loop over all fields of the current class under inspection and check for the @InjectView annotation. In the event we find one, we register a custom com.google.inject.MembersInjector to inject values from the view binding map we pass in the constructor of the TestViewMembersInjector.

Now, on to the very last code snippet. Take a look at the implementation of the TestViewMembersInjector:

public class TestViewMembersInjector implements MembersInjector { private SparseArray viewBindings; private Field field; public TestViewMembersInjector(SparseArray viewBindings, Field field) { this.viewBindings = viewBindings; this.field = field; } @Override public void injectMembers(T instance) { InjectView injectView = field.getAnnotation(InjectView.class); final int id = injectView.value(); field.setAccessible(true); Object view = viewBindings.get(id); if (view != null) { try { field.set(instance, view); } catch (Exception e) { throw new IllegalStateException(e); } } } }

The TestViewMembersInjector uses reflection to set a value on the instance Guice passes to us in the injectMembers() method. We inject into the field we have passed in the class constructor and take the value from the viewBindings which maps from the View ID to the actual object we have added as a binding in the setUp() method of our test.

The approach of getting the field to bind to the implementation of our actual binding is a little rough but is good enough for test execution. For a more elaborate approach I suggest having a look at the roboguice.inject.ViewListener class that performs a similar action for production code.

Wrap Up

Ok, if you have made it this far you must be seriously interested in RoboGuice and testing your applications with Robolectric and Mockito. In the end it should have become clear that using RoboGuice can help you to remove the clutter from your production code by injecting required dependencies. In addition the test code becomes much more elegant while not requiring you to break any code encapsulation with test-only setters or getters.

There are a couple of things to keep in mind though. When you decide to use RoboGuice you should attempt to inject everything because once you break the chain of injection, you will have to trigger injection manually which is not very elegant. Also when writing tests, you should inject the class under test if you don’t want to call the inject call manually.

And finally, if you use RoboGuice together with the popular ActionBarSherlock, you face the problem that they both require you to inherit from their respective Activity/Fragment/Provider etc classes. One solution is the library roboguice-sherlock. It combines the two frameworks into one base Activity/Fragment/Provider.

Happy injecting/shadowing/mocking and don’t forget to follow me on Google+: gplus.to/mpost