Better Android Testing with Robolectric 2.0

May 09, 2013

As we have mentioned before on this blog, verifying the behavior of our applications is an essential part of our day-to-day development. Robolectric is a framework which facilitates writing unit tests for your Android code, which we use extensively at Square. Today we are happy to announce the availability of Robolectric 2.0 release candidate 1!

Why Test?

There is virtually no reason why you should not be testing. Some advantages are:

  • Testing increases the level of confidence that you have in your code and in the quality of the application or library that you are shipping.
  • Testing makes it possible to write new code, and refactor existing code, without worrying that you've broken existing functionality. This is especially critical in an environment with multiple developers.
  • Testing encourages you to write more modular, self-contained code to simplify writing tests. Extremely dense and complex code is difficult to test, while smaller code blocks are not only easier to test but increase readability and reduce bugs.

With Robolectric

It's always been possible to test Android code with solutions like instrumentation tests or by mocking out the majority of the platform classes, but both of these strategies come with significant drawbacks: instrumentation tests are clunky and prone to spurious failure, while mock-heavy tests are hard to maintain and can give false confidence.

Robolectric takes a different approach: it sits between the Android OS code and your tests to enable as much real behavior as possible, right in your regular desktop JVM, within your IDE.

Most code that you write using Robolectric as your test runner will operate exactly as it would if running on a real device. Views, fragments, bitmaps, resources, and more are now available in tests, which means the focus can be on verifying the logic and behavior of your code.

What's New in 2.0

Robolectric 2.0 is dramatically better at emulating the Android OS, because it uses real Android SDK code (modified so it can run on a desktop JVM) behind the scenes. Styles and themes are now supported, and system resources are available. The startup time for tests is significantly improved from the 1.x releases. Numerous bugs have been fixed.

An Example

Let's write some code to modify a Bitmap.

/** Crop a bitmap into a square. The original will be recycled. */
public static Bitmap cropSquare(Bitmap source) {
  int width = source.getWidth(), height = source.getHeight():
  int top = 0, left = 0;
  int size;
  if (width > height) {
    left = (width - height) / 2;
    size = height;
  } else {
    top = (height - width) / 2;
    size = width;
  }
  Bitmap result = Bitmap.createBitmap(source, top, left, size, size);
  source.recycle();
  return result;
}

Now we'll test this method with some simple examples.

¢RunWith(RobolectricTestRunner.class) public class CropperTest {
  ¢Test public void shouldCropBitmaps() {
    Bitmap wide = Bitmap.createBitmap(100, 10, ARGB_8888);
    Bitmap wideResult = cropSquare(wide);
    assertThat(wideResult).hasWidth(10).hasHeight(10).isNotRecycled();

    Bitmap tall = Bitmap.createBitmap(10, 100, ARGB_8888);
    Bitmap tallResult = cropSquare(tall);
    assertThat(tallResult).hasWidth(10).hasHeight(10).isNotRecycled();

    Bitmap square = Bitmap.createBitmap(10, 10, ARGB_8888);
    Bitmap squareResult = cropSquare(square);
    assertThat(squareResult).hasWidth(10).hasHeight(10).isNotRecycled();
  }
}

(Like these declarative assertions? Check out FEST Android)

Our first two assertions pass but the last one fails! Turns out we overlooked a crucial aspect of the [documentation on Bitmap.createBitmap][bmp]: "The new bitmap may be the same object as source, or a copy may have been made." (emphasis added)

The code above was accidentally recycling the original Bitmap when it was returned because it was already a square. Let's update our code to support this.

public static Bitmap cropSquare(Bitmap source) {
  ...
  Bitmap result = Bitmap.createBitmap(source, top, left, size, size);
  if (result != source) {
    source.recycle();
  }
  return result;
}

And now all three assertions pass! Moving forward we never have to worry about verifying this case again.

Testing your Application Under Different Device Configurations

Robolectric lets your modify Android's configuration for each test, so you can check that your app
works correctly in different locales, with different screen sizes and orientations, etc.

¢Test ¢Config("en")
public void shouldGiveEnglishResponses() {
  assertThat(activity.findViewById(R.id.message)).hasText("Hello!");
}

¢Test ¢Config("fr")
public void shouldGiveFrenchResponses() {
  assertThat(activity.findViewById(R.id.message)).hasText("Bonjour!");
}

Get Testing!

The above example only scratches the surface of what's possible with Robolectric. Some of the things we test in our apps are upgrade paths from old storage formats, network communication failures, and custom widgets. Running these as unit tests on every build gives us confidence that these edge-cases are covered and allows us to focus on other aspects of development.

To get started, visit the Robolectric website and download the release candidate.

This post is part of Square's "Seven Days of Open Source" series.

[bmp]: https://developer.android.com/reference/android/graphics/Bitmap.html#createBitmap(android.graphics.Bitmap, int, int, int, int)

Christian Williams
Square engineer, and not at all obsessed with sheep. @AntiXian666
Jake Wharton
Android engineer and open source hacker. @JakeWharton

Comments

Get support help at squareup.com/support. We'll delete off-topic comments.