Simple BDD Android Testing with Robolectric

February 24, 2015 Joe Moore

At Pivotal Labs, we’re all about TDD and BDD. Android testing is no exception. On a recent Android project we relied on the trusty combination of JUnit and Robolectric for fine-grained unit tests. We were excited to try the Espresso BDD framework for Android, as we were attracted to its syntax and ability to drive the emulator. Unfortunately, we were forced to abandon Espresso. Espresso didn’t play well with our DrawerLayout for reasons that seemed insurmountable; for example, the Drawer would freeze in a half-open state and lock up the emulator if the computer’s CPU load spiked.

We didn’t want abandon BDD, and we decided to use JUnit and Robolectric for these tests, too, but in a different style. Here is the pattern that worked well for us.

Thinking Outside-In

It’s important to remember that we’re not introducing a new framework or library: we’re just choosing to use Robolectric with an eye for outside-in testing. Things to keep in mind:

  • Your screen is full of Views, such as Button, TextView, and EditText elements. Use the Activity to find those elements and interact with them as similarly as you can to how a real user would interact.
  • Try not to use Activity’s handy public methods, such as callback method. If a Button would invoke a callback, use Robolectric to clickOn the button, and assert the side effects.

That said, we were pragmatic. For example, we did not trigger individual keyboard events to simulate typing.

Start with the Story

First, we started with the business Story, which is written in the Gherkin business language. We need our users to be able to edit their Profile.

pivotal tracker story

Lets assume we want some screens that look something like the following:

Page 2 annotatedPage 3 edit annotatedPage 4 does not exist

Write the Test Outline

In other languages it’s easy (well, easier) to write natural-language style tests that interoperate Gherkin, such as Ruby’s Cucumber. Lacking this, We started by creating a test with the Story as a massive comment within the test. Here’s the skeleton of the test:


@Test
//As a User
//I need to be able to update my profile
//So I can keep my information up to date

public void user_can_can_update_her_profile() {
//Given I am a User
//When I am on the Home screen
//And I tap the Profile button
//Then I am taken to the profile screen

//When I am on my profile page
//Then I can view my current profile info

//When I tap the Edit button
//Then I see the Edit Profile screen
//And I see my current profile information
//
//When I leave a required field blank
//And I tap the Save button
//Then I should see a Toast stating "You must fill out all required fields"

//When I fill out all the required fields
//And I tap Save
//Then my information should be updated
//And I should see a toast stating "Profile updated!"
}

Work Your Way Down

Let’s fill out the test assuming that the Home screen and Profile screens are functional. No problems here:


//Given I am a User
//When I am on the Home screen
HomeActivity homeActivity = Robolectric.setupActivity(HomeActivity.class);

//And I tap the Profile button
clickOn(homeActivity.findViewById(R.id.profile_button));

//Then I am taken to the profile screen
ProfileActivity profileActivity = (ProfileActivity) verifiedTransitionToActivity(homeActivity, ProfileActivity.class);
// ...
}

We’re making progress! BTW verifiedTransitionToActivity is a helper method we wrote to make sure that we are actually transitioning from one activity to another:


private Activity verifiedTransitionToActivity(Activity fromActivity, Class<? extends Activity> toActivityClass) {
  assertThat(shadowOf(fromActivity).getNextStartedActivity()).hasComponent(fromActivity, toActivityClass);
  return Robolectric.setupActivity(toActivityClass);
}

 

Coding By Wishful Thinking

Now let’s test for functionality that does not exist yet: the Edit Profile button:


//...
//And I tap the Edit button
clickOn(profileActivity.findViewById(R.id.edit_button));
// BOOM! The Edit button does not exist yet!

page 3 no button

Now we’re really driving out the Story! The above code won’t even compile without an R.id.edit_button. We know we need an Edit button on the screen, but we don’t have one — we’re coding by wishful thinking. It’s time “dive down a level” so to speak, likely to the ProfileActivityTest — we must have tests for that Activity, since it already exits, right? Of course! We’ll unit test the existence of the Edit button there. When we’re confident that we’ve implemented the Edit button, we “pop back up to the top” to the BDD test and make sure succeeds past the most recent failure.

Break it Until It Works

Now that the Edit button exists, let’s make sure it does something when tapped:


//And I tap the Edit button
clickOn(profileActivity.findViewById(R.id.edit_button));

//Then I see the Edit Profile page
EditProfileActivity editProfileActivity = (EditProfileActivity) verifiedTransitionToActivity(profileActivity, EditProfileActivity.class);
// BOOM Again!!!! EditProfileActivity doesn't exist.

Like the previous example, we can “code by wishful thinking” and write that we’d like a EditProfileActivity even if we don’t have one. From here we’ll create a EditProfileActivityTest and keep going from there.

You get the picture. Keep working your way down the story, filling out each line using wishful thinking and user-style interactions as much as possible, diving down to lower levels of the implementation stack (and test stack) whenever your test fails to compile or assertions fail. We’re writing just enough to get our BDD test passing past it’s most recent failure.

When the entire BDD test passes, you should be done implementing the feature. If not, write more tests!

About the Author

Biography

More Content by Joe Moore
Previous
How To: Configuring Nagios to Monitor Pivotal Cloud Foundry
How To: Configuring Nagios to Monitor Pivotal Cloud Foundry

In this in-depth how-to article, Chris Mattingly, Solutions Architect from EMC, provides background, overvi...

Next
Pivotal Podcast #14: The Need for Speed With In-Memory Data Grids—All About GemFire
Pivotal Podcast #14: The Need for Speed With In-Memory Data Grids—All About GemFire

Enter curious. Exit smarter.

Learn More