This post is opinion.
Let’s look at the
verify method in Mockito for testing in Java.
verify(myMock).someFunction(123) – expects that
someFunction has been called on the mock ONCE with the input
These days I prefer the full
BDDMockito alternative, so write
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.
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:
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 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.
Let’s use an
ArgumentCaptor to solve the email regular expression problem.
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.
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.