Robolectric 2.2: Some Pages from the Missing Manual

When I have a conversation about Android development with anyone I usually end up complaining about how hard and annoying testing is. I think Google did a huge disservice to the community by neglecting testing infrastructure since as a result there is no culture of testing in Android development. There are some open source efforts to improve the situation though, and the main tool that has been preserving bits of my sanity while coding for Android is Robolectric.

I use it mostly to write unit tests for activity and fragment classes, since doing that with the tools provided by Android is really rather painful (and slow). With the just released Robolectric version (2.2) I feel its API, especially around maintaining the activity lifecycle, has finally evolved to where tests are really easy and intuitive to write. There is some basic documentation for the new API but I haven’t come across good real world examples, so I thought it could be useful to document some patterns I’ve been using.

Testing Activities

My activity tests tend to have the following shape, here for a fictional class MyActivity:

@RunWith(RobolectricTestRunner.class)
public class MyActivityTest {

    private ActivityController controller;
    private MyActivity activity;

    @Before
    public void setUp() {
    	controller = Robolectric.buildActivity(MyActivity.class);
    }

    @After
    public void tearDown() {
    	controller.destroy();
    }

    private void createWithIntent(String myExtra) {
        Intent intent = new Intent(Robolectric.application,
                MyActivity.class);
    	Bundle extras = new Bundle();
    	extras.putString("myExtra", myExtra);
    	intent.putExtras(extras);
    	activity = controller
    		.withIntent(intent)
    		.create()
    		.start()
                .visible()
    		.get();
    }

    @Test
    public void createsAndDestroysActivity() {
    	createWithIntent("foo");
        // Assertions go here
    }

    @Test
    public void pausesAndResumesActivity() {
    	createWithIntent("foo");
    	controller.pause().resume();
        // Assertions go here
    }

    @Test
    public void recreatesActivity() {
    	createWithIntent("foo");
    	activity.recreate();
        // Assertions go here
    }

}

In Robolectric 2.2, ActivityController is now the canonical API (actually, the only API) for this sort of test. It has methods that make the underlying activity go through state changes. To fully create an activity as it would be presented to a user you generally want to call create(), start() and visible() on it. I’m also putting a destroy() call in an @After method to ensure tearing down the activity always works properly.

As a rule, every activity test I write has at least the 3 tests above, covering the most common state transitions – create/destroy, pause/resume, and recreate. You might be surprised how often you initially implement broken recreate logic. On an actual device, a recreate happens for example when the screen rotates, something that’s quite common but easily overlooked in manual testing.

Often I’ll want to write multiple tests that start an activity with different intents. This is hinted at in the createWithIntent() method which takes a customizable extra parameter for the intent. For a very simple class you might omit that and just create the activity without any intent in the setUp() method.

Testing Fragments

For fragment unit tests I am using a base class that can make any fragment go through the lifecycle of a parent activity:

public class FragmentTestCase {

    private static final String FRAGMENT_TAG = "fragment";

    private ActivityController controller;
    private FragmentActivity activity;
    private T fragment;

    /**
     * Adds the fragment to a new blank activity, thereby fully
     * initializing its view.
     */
    public void startFragment(T fragment) {
        this.fragment = fragment;
        controller = Robolectric.buildActivity(FragmentActivity.class);
        activity = controller.create().start().visible().get();
        FragmentManager manager = activity.getFragmentManager();
        manager.beginTransaction()
        		.add(fragment, FRAGMENT_TAG).commit();
    }

    @After
    public void destroyFragment() {
        if (fragment != null) {
            FragmentManager manager = activity.getFragmentManager();
            manager.beginTransaction().remove(fragment).commit();
            fragment = null;
            activity = null;
        }
    }

    public void pauseAndResumeFragment() {
    	controller.pause().resume();
    }

    public T recreateFragment() {
    	activity.recreate();
        // Recreating the activity creates a new instance of the
        // fragment which we need to retrieve by tag.
    	fragment = (T) activity.getFragmentManager()
    			.findFragmentByTag(FRAGMENT_TAG);
    	return fragment;
    }

}

The general idea is that the best way to put a fragment through realistic state transitions is to add it to a real activity and then make that activity change states. Using a blank FragmentActivity works well for this purpose.

The helper methods allow me to very quickly write at least 3 tests covering the most common transitions, same as above for activities. This is an example for a MyFragment class:

@RunWith(RobolectricTestRunner.class)
public class MyFragmentTest extends FragmentTestCase<MyFragment> {

    private MyFragment fragment;

    @Before
    public void setUp() {
        fragment = new MyFragment();
    }

    @Test
    public void createsAndDestroysFragment() {
        startFragment(fragment);
        // Assertions go here
    }

    @Test
    public void pausesAndResumesFragment() {
        startFragment(fragment);
        pauseAndResumeFragment();
        // Assertions go here
    }

    @Test
    public void recreatesFragment() {
        startFragment(fragment);
        fragment = recreateFragment();
        // Assertions go here
    }

}

***

I’ve had pretty solid results with these patterns so far. I’m beta testing an app with real users right now, fully covered with tests like the above, and I haven’t seen any crashes yet (knock on wood). But I’m sure this can be improved upon. I’d be curious to hear how others use Robolectric since I can find so few real world examples out there.

One thought on “Robolectric 2.2: Some Pages from the Missing Manual

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s