Recently, during a code review at work there was a discussion about
whether Mockito’s verify
is necessary when when
(or given
,
which is a synonym) is parameterized. The quick answer is "no".
Imagine this contrived example. I have two classes, Calculator
and
ComplexOperationService
, where the latter requires the former. The
goal is to write a unit test for ComplexOperationService
and mock
all usages of Calculator
.
Here are the two classes.
public class Calculator {
public int sum(final int a, final int b) {
return a + b;
}
}
@RequiredArgsConstructor
public class ComplexOperationService {
private final Calculator calculator;
public void doComplexOperation(final int a, final int b) {
System.out.println(calculator.sum(a, b));
}
}
And here are two types of tests you can write. First, only by using
when
. This is basically the succinct variant. In this case the mock
will only return a result if it is called with the exact parameters.
Otherwise: error. More on that in a bit.
@Test
public void verify_with_when()
{
when(calculator.sum(40, 2)).thenReturn(42);
complexOperationService.doComplexOperation(40, 2);
}
The longer version is to use verify
instead. In this small example
it doesn’t amount to much more, but imagine you have five when
s and
five corresponding verify
s. At some point it is getting verbose.
What is happening here is that I’m saying that I don’t care about the
parameters passed to the mock. Just always return a result when the
method is called. Then, in a separate step, explicitly check that the
method has been called with specific parameters. This is how I prefer
it, despite having to write a bit more.
@Test
public void verify_with_verify_anyInt_fails()
{
when(calculator.sum(anyInt(), anyInt())).thenReturn(42);
complexOperationService.doComplexOperation(20, 1);
verify(calculator).sum(40, 2);
}
Apart from style, what are the differences? The quick answer is "error messages".
Let’s start with the verify
example and make it fail.
@Test
public void verify_with_verify_anyInt_fails()
{
when(calculator.sum(anyInt(), anyInt())).thenReturn(42);
complexOperationService.doComplexOperation(20, 1);
verify(calculator).sum(40, 2);
}
The corresponding error message says:
Wanted but not invoked:
calculator.sum(40, 2);
-> at com.thecodeslinger.CalculatorTest.verify_with_verify_anyInt_fails(CalculatorTest.java:49)
However, there was exactly 1 interaction with this mock:
calculator.sum(20, 1);
A method call with parameters "40" and "2" was expected but "20" and
"1" have been provided. Pretty straightforward. Before moving on the
the when
version, let’s have a look at the error message when
calculator.sum()
isn’t called at all.
Wanted but not invoked:
calculator.sum(40, 2);
-> at com.thecodeslinger.CalculatorTest.verify_with_verify_anyInt_fails_no_call(CalculatorTest.java:62)
Actually, there were zero interactions with this mock.
Also, truly clear what is happening here.
Allright, now we’ll take a look at how Mockito reacts when the
when
cases fail.
@Test
public void verify_with_when_fails()
{
when(calculator.sum(40, 2)).thenReturn(42);
complexOperationService.doComplexOperation(20, 1);
}
And the error message. Wait! Is that a usage error of Mockito?
org.mockito.exceptions.misusing.PotentialStubbingProblem:
Strict stubbing argument mismatch. Please check:
- this invocation of 'sum' method:
calculator.sum(20, 1);
-> at com.thecodeslinger.ComplexOperationService.doComplexOperation(ComplexOperationService.java:11)
- has following stubbing(s) with different arguments:
1. calculator.sum(40, 2);
-> at com.thecodeslinger.CalculatorTest.verify_with_when_fails(CalculatorTest.java:32)
Typically, stubbing argument mismatch indicates user mistake when writing tests.
Mockito fails early so that you can debug potential problem easily.
However, there are legit scenarios when this exception generates false negative signal:
- stubbing the same method multiple times using 'given().will()' or 'when().then()' API
Please use 'will().given()' or 'doReturn().when()' API for stubbing.
- stubbed method is intentionally invoked with different arguments by code under test
Please use default or 'silent' JUnit Rule (equivalent of Strictness.LENIENT).
Let’s quickly also look at the message when the mock isn’t called.
org.mockito.exceptions.misusing.UnnecessaryStubbingException:
Unnecessary stubbings detected.
Clean & maintainable test code requires zero unnecessary code.
Following stubbings are unnecessary (click to navigate to relevant line of code):
1. -> at com.thecodeslinger.CalculatorTest.verify_with_when_fails_no_call(CalculatorTest.java:39)
Please remove unnecessary stubbings or use 'lenient' strictness. More info: javadoc for UnnecessaryStubbingException class.
So… Technically Mockito is correct. But it makes it hard to determine whether there is a bug in how the tests have been written or in the logic that is being tested.
This, of course, isn’t the only difference in using when
and
verify
. There is much more to it. For example, using verify
you
can say how many times it is expected that the method is being called.
// times(1) is the implicit default.
verify(calculator, times(2)).sum(40, 2);
I’m not going into more detail on what you can do with when
and
verify
. The point of this little experiment was to find out whether
it could be enough to write when
and use that to verify the method
has been called with the expected parameters. The quick answer is
"yes".
However, as I have shown, the error message is not immediately helpful. Unless you’ve encountered this message in a similar situation before and also haven’t forgotten about it you might wonder where the error comes from all of a sudden.
Apart from the error message, I also prefer using verify
to
explicitly state my intention. At my former employer I have worked
with the Grails framework and for testing it uses Spock. This
requires you to structure tests in a specific way.
given:
// Variables, test setup
when:
// The thing that is being tested
then:
// Asserts and method call verifications
I have adopted this layout to Java and the way I do it is to specify
mock invocations in the "given" section using Mockito’s given
instead of when
and then verify
the invocations in the "then"
section. Since there are no keywords in Java for given:
, when:
and then:
I use comments to separate the three sections.
(I didn’t do it in the sample project because of brevity)
Therefore, my tests usually look like this:
@Test
public void verify_with_verify_anyInt()
{
// Given
given(calculator.sum(anyInt(), anyInt())).willReturn(42);
// When
complexOperationService.doComplexOperation(40, 2);
// Then
verify(calculator).sum(40, 2);
}
This helps me a lot in visually separating setup, the tested code and
verification of result and mocks. I find it hard to read and
understand tests so I try to make it as easy as I can. I know that
adding a bunch of verify
adds a lot more code. However, as long as
it is separated as clearly as I do it, I still prefer it.
I’ve seen colleagues write more complex tests where assert
s are
baked into the when
calls, e.g. to extract parameters from an
invocation object.
Pseudocode:
when(calculator.sum).thenAnswer(invocation -> {
var a = (int)invocation.getArgument(0);
var b = (int)invocation.getArgument(1);
assert(40, a);
assert(2, b);
});
Of course, this kind of assert doesn’t add anything to the one-liner
when(calculator.sum(40, 2)).thenReturn(42);
. Just imagine there’s
much more complex logic happening there. The problem I have is that
verification is happening before the code under test is called.
This means I have to keep the mock and the verification in my
head until I see what is being tested. And then there may be more
verification and I have to check two locations (or even more) to make
sure everything has been tested and verified.
It’s like judge, jury and executioner, only in a different order. Judge, executioner and then jury. I’m not saying that this is a bad style and sometimes there may be legitimate reasons to do so. I, personally, try to keep my particular ordering because it helps me. I have even convinced a colleague to write similarly structured tests, not by long discussions, but simply by writing tests this way and he reviewed them.