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