Safely mocking consecutive invocations with Mockito

A quick tip to fix a mocking gotcha.

August 13, 2019 - 3 minute read -
java mockito testing mocking tip

Sometimes you want to mock the behaviour of a class to give different responses to each invocation of a method.

For example, if you wanted to arrange a test to verify the ability of a class; Syncer to recover from a RuntimeException thrown by a service it uses, your test might look a bit like this:

@Test
public void getData_shouldReturnSyncedData_whenSyncingAfterPreviousSyncFails() {
    // given
    UnreliableService mockService = mock(UnreliableService.class);
    when(mockService.getApiData()).thenThrow(new RuntimeException("boom"));  // arrange a failed sync
    Syncer syncer = new Syncer(mockService);
    syncer.sync();                                                           // trigger the failed sync
    when(mockService.getApiData()).thenReturn("a value");                    // arrange a successful sync

    // when
    syncer.sync();

    // then
    assertThat(syncer.getSyncData()).isEqualTo("a value");
}

So you run this test and it fails. Not always a bad thing when developing tests; at least you know it’s doing something! But in this case, the test case is failing on this line:

when(mockService.getApiData()).thenReturn("a value");

This isn’t an assertion, so we’ve likely got something wrong in our test arrangement.

The error indicates that we’re actually throwing a RuntimeException from our test.

java.lang.RuntimeException: boom

	at com.petertackage.example.ExampleTest.getData_shouldReturnSyncedData_whenSyncingAfterPreviousSyncFails(ExampleTest.java:18)

Process finished with exit code 255

So what’s going on and what can we do about it?

Well, the problem lies in the fact that in our attempt to mock the getApiData method for a second time, we are, perhaps surprisingly, invoking that method and getting the RuntimeException we told it to throw in our previous mocking. It’s right there!

To fix this we need to define both mock invocations without triggering the mocked method behaviour.

Luckily we can do this safely by chaining the mocking declarations like this:

when(mockService.getApiData()).thenThrow(new RuntimeException("boom"))
                              .thenReturn("a value");

This prevents the RuntimeException from being thrown prematurely and makes our test case neater too:

@Test
public void getData_shouldReturnSyncedData_whenSyncingAfterPreviousSyncFails() {
    // given
    UnreliableService mockService = mock(UnreliableService.class);
    when(mockService.getApiData()).thenThrow(new RuntimeException("boom"))
                                  .thenReturn("a value");
    Syncer syncer = new Syncer(mockService);
    syncer.sync();

    // when
    syncer.sync();

    // then
    assertThat(syncer.getSyncData()).isEqualTo("a value");
}

We can chain as many mock behaviours as we like, although if you have more than a few it might be worth thinking about the complexity of the class you are trying to test.

Thanks for reading!

This post is Creative Commons Attribution 4.0 International (CC BY 4.0) licensed.