Mockito on Android

October 22, 2012

At Square we love unit tests. Our comprehensive test suite helps us to develop Android apps with confidence.

But without proper tools many scenarios are cumbersome to unit test. For example, suppose we want to pump up the device’s volume in Square Wallet. We have a method that cranks the ringer up to eleven unless the phone is silent:

public void maximizeVolume(AudioManager audioManager) {
  if (audioManager.getRingerMode() != RINGER_MODE_SILENT) {
    int max = audioManager.getStreamMaxVolume(STREAM_RING);
    audioManager.setStreamVolume(STREAM_RING, max, 0);
  }
}

We’d like to test that this method does the right thing in all situations. If the phone is silent then the volume should not be changed. Otherwise the volume should be set to whatever getStreamMaxVolume() returned.

Manually testing scenarios like these is strenuous and error-prone. Unit testing usually requires ugly and brittle boilerplate to intercept the AudioManager APIs. Mock objects make testing easy by allowing you to script responses and validate interactions.

Mockito is my favorite library for testing with mock objects. Its fluent API separates pre-test preparation from post-test validation. Should the test fail, Mockito makes it clear to see where our expectations differ from reality! The library has everything you need to write complete tests.

A new feature in Mockito 1.9.5 is support for Android’s Dalvik runtime. It uses DexMaker to generate mock classes on the fly. To use Mockito on a device or emulator, you’ll need to add three .jar files to your test project’s libs directory: mockito-all-1.9.5.jar, dexmaker-1.0.jar, and dexmaker-mockito-1.0.jar.

Mockito tests have three parts:
1. Prepare the mocks and script their behavior.
1. Test the code of interest. When this code calls a method on the mock, the mock will consult its script and respond accordingly. If it has nothing scripted, it returns a default value like 0 or null.
1. Validate that the mocks saw what they were expecting to see. And also that they didn’t see anything unexpected!

This example code tests one of the scenarios above:

public void testSilentRingerIsNotDisturbed() {
  // 1. Prepare mocks and script their behavior.
  AudioManager audioManager = Mockito.mock(AudioManager.class);
  Mockito.when(audioManager.getRingerMode())
      .thenReturn(RINGER_MODE_SILENT);

  // 2. Test the code of interest.
  maximizeVolume(audioManager);

  // 3. Validate that we saw exactly what we wanted.
  Mockito.verify(audioManager).getRingerMode();
  Mockito.verifyNoMoreInteractions(audioManager);
}

Mockito’s fluent API may take some getting used to! Its methods like when() and verify() use some clever Java generics to achieve both readability and type safety. Once you’re comfortable with this approach, use static imports to make the code even more fluent:

import static org.mockito.Mockito.*;

...

public void testNormalRingerIsMaximized() {
  // 1. Prepare mocks and script their behavior.
  AudioManager audioManager = mock(AudioManager.class);
  when(audioManager.getRingerMode()).thenReturn(RINGER_MODE_NORMAL);
  when(audioManager.getStreamMaxVolume(STREAM_RING)).thenReturn(100);

  // 2. Test the code of interest.
  maximizeVolume(audioManager);

  // 3. Validate that we saw exactly what we wanted.
  verify(audioManager).setStreamVolume(STREAM_RING, 100, 0);
}

We’ve created a simple test that always works, regardless of how the device is configured. Since the test only ever interacts with a mock audio manager, the device itself is not impacted by tests: no more missed phone calls after running JUnit!

Mock objects are a powerful tool for scenario testing. Learn more about mock object patterns (and antipatterns!) on Martin Fowler’s Mocks Aren’t Stubs article. See the Mockito site for specific APIs and examples.

Jesse Wilson
Android developer at Square in Waterloo, Canada. Maker of testing tools DexMaker and MockWebServer.

Comments

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