Mockito Matchers Precedence

This post is opinion.

Let’s look at the verify method in Mockito for testing in Java.

Example: verify(myMock).someFunction(123) – expects that someFunction has been called on the mock ONCE with the input 123.

These days I prefer the full BDDMockito alternative, so write then(myMock).should().someFunction(123).

Same basic concept.

The Three Matching Methods

You can provide the value into the verifying function chain with three different mechanisms:

  • object/literal value
  • argument matcher
  • argument captor

In my opinion, the above is also the order of precedence with the captor being something of last resort. Let’s explore the mechanisms.

Concrete Tests Are Best

Ideally, you have defined your test theoretically as something like – given this input, when the system runs, then the output is X. When we’re verifying outbound function calls, we run the risk of testing that the lines of implementation are present, rather than testing the behaviour, but it’s reasonable to say that if the system is behaving right, then we’d expect something to be sent to some target or another.

Generally, if we design our module to have a clear input and a clear measurable output, then you can predict what should be output with a given input.

Example:

EmailBuilder builder = new EmailBuilder(mockEmailObject);
builder.setRecipients("me@you.com, him@her.com, it@them.com");

then(mockEmailObject)
    .should()
    .addRecipient("me@you.com");
then(mockEmailObject)
    .should()
    .addRecipient("him@her.com");
then(mockEmailObject)
    .should()
    .addRecipient("it@them.com");

Note: I’ve not told you anything about the surrounding code here, but I’m guessing you can read the expected behaviour of setRecipients from the simple test.

This is why concrete test data speaks volumes in tests and is our first and most simple approach.

When The Data Is Not Important

There comes a point where it’s not the value of the input that we care about, so much as the nature of it. In the above example, maybe some of our tests can skip over WHICH email addresses are used, and instead care about a higher level concern, like whether any calls were made, or how many.

Had I seen this in a unit test, I wouldn’t have been shocked:

verify(mockEmailObject, times(3)).addRecipient(anyString());

Here an argument matcher is being used to assert more vaguely, but perhaps that’s good enough. Locking everything down to concrete data can make tests more fragile, and while it’s worth doing for the low-level algorithms that need clear input/output mappings, it can be ok to drop down to a more vague assertion higher up, as you care less about the exact values.

We could use Mockito’s argThat here.

verify(mockEmailObject, times(3))
    .addRecipient(argThat(recipient -> 
        recipient.matches("[a-z]+@[a-z]+\\.com")));

The argThat matcher allows us to use a Java Predicate to provide some logic about the expectation. This allowed us to use a regular expression here to check that the email addresses were correct (within the confines of this test data). This trick is useful for testing with generated values like GUIDs or timestamps.

We can also use argThat to select fields from the input to check.

However, when you want to do complex assertions on the object that’s sent to the mock function, the instinct is to use ArgumentCaptors. I still think of them as a last resort.

Captivated Captors

Let’s use an ArgumentCaptor to solve the email regular expression problem.

// in the instance variable section of the test:
@Captor // assuming you're using MockitoExtension/MockitoJUnitRunner... DO!
private ArgumentCaptor<String> stringCaptor;

@Mock
private Email mockEmailObject;

@Test
void whenAddingRecipientsFromToLine_thenEachIsAddedSeparately() {
    EmailBuilder builder = new EmailBuilder(mockEmailObject);
    builder.setRecipients("me@you.com, him@her.com, it@them.com");

    then(mockEmailObject)
        .should(times(3))
        .addRecipient(stringCaptor.capture());

    stringCaptor.getAllValues()
        .forEach(value -> assertThat(value).matches("[a-z]+@[a-z]+\\.com");
}

In some articles, the above would be the denouement of the discussion. The full blown bells and whistles example. Wow. Look on how it builds up to an amazing creation…! But…

While the above does illustrate how the captor can be used, and shows you how you can pluck all calls, or a single one, and then do any assertion you like on it with your favourite assertion library, see how it compares to the previous two examples.

Comparison

The concrete example was:

  • When it’s called
  • Then you get a call with value A
  • And one with value B
  • And one with value C

The matcher example had:

  • When it’s called
  • Then you get three calls that match this expression

The argument capture example was:

  • When it’s called
  • Then you get three calls – REMEMBER THEM
  • And when you inspect the values of those calls
  • Then they match these assertions

Note: the latter test stutters at the argument capturing. The then step needs some extract doings after it, in terms of inspecting the arguments captured. As such, it’s a tool for a specific purpose, one where embedding the assertion in argThat or one of the built in matchers is not powerful enough, or doesn’t provide meaningful test failure output.

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 )

Google photo

You are commenting using your Google 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 )

Connecting to %s