Core Java

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:

01
02
03
04
05
06
07
08
09
10
11
12
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:

1
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.

1
2
3
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.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
// 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.

Published on Java Code Geeks with permission by Ashley Frieze, partner at our JCG program. See the original article here: Mockito Matchers Precedence

Opinions expressed by Java Code Geeks contributors are their own.

Ashley Frieze

Software developer, stand-up comedian, musician, writer, jolly big cheer-monkey, skeptical thinker, Doctor Who fan, lover of fine sounds
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button