I encountered to a very strange behavior in case I change the mock behavior during the test. I mock a very simple interface:
interface Bar {
String string(String str);
}
@Mock
private Bar bar;
Then I call it and count the number of invocations using AtomicInteger
, which serves as a side-effect for this minimal working example.
@Test
public void test() {
AtomicInteger atomicInteger = new AtomicInteger(0);
// Mock with the increment
Mockito.when(bar.string(Mockito.anyString())).then(invocation -> {
log.info("MOCK - waiting (1): {}", invocation.getArguments()[0]);
atomicInteger.incrementAndGet();
log.info("MOCK - returning (1)");
return "BAR_1";
});
// Invocation of the increment
log.info("Result (1): " + bar.string("FOO_1"));
// Passes
Assertions.assertEquals(1, atomicInteger.get());
}
14:18:17.336 [main] INFO com.Foo - MOCK - waiting (1): FOO_1
14:18:17.343 [main] INFO com.Foo - MOCK - returning (1)
14:18:17.349 [main] INFO com.Foo - Result (1): BAR_1
The test passes as long as the method was obviously invoked once using bar.string("FOO_1")
. As long as I add the new behavior of the mock bar
after this executes to not increment the AtomicInteger
, there happens one more invocation of the original mock that should not be called:
@Test
public void test() {
AtomicInteger atomicInteger = new AtomicInteger(0);
// Mock with the increment
Mockito.when(bar.string(Mockito.anyString())).then(invocation -> {
log.info("MOCK - waiting (1): {}", invocation.getArguments()[0]);
atomicInteger.incrementAndGet();
log.info("MOCK - returning (1)");
return "BAR_1";
});
// Invocation with increment
log.info("Result (1): " + bar.string("FOO_1"));
/* NEW CODE BLOCK STARTS */
// Mock without the increment
Mockito.when(bar.string(Mockito.anyString())).then(invocation -> {
log.info("MOCK - returning (2): {}", invocation.getArguments()[0]);
return "BAR_2";
});
// Invocation without the increment
// The previous lines really changed the mock, but it was called one more times
log.info("Result (2): " + bar.string("FOO_2"));
/* NEW CODE BLOCK ENDS */
// Fails, it is 2
Assertions.assertEquals(1, atomicInteger.get());
}
14:19:31.603 [main] INFO com.Foo - MOCK - waiting (1): FOO_1
14:19:31.612 [main] INFO com.Foo - MOCK - returning (1)
14:19:31.620 [main] INFO com.Foo - Result (1): BAR_1
14:19:31.621 [main] INFO com.Foo - MOCK - waiting (1):
14:19:31.621 [main] INFO com.Foo - MOCK - returning (1)
14:19:31.623 [main] INFO com.Foo - MOCK - returning (2): FOO_2
14:19:31.624 [main] INFO com.Foo - Result (2): BAR_2
Surprisingly, the log shows the mocked method was called with no parameter on the 4th line.
The behavior doesn't change when I include more of this block of code within the same test N
-times. The test always fails expecting the increment to be 2
instead of 1
.
Mockito.when(bar.string(Mockito.anyString())).then(invocation -> {
log.info("MOCK - returning (N): {}", invocation.getArguments()[0]);
return "BAR_N";
});
log.info("Result (N): " + bar.string("FOO_N"));
What makes Mockito call exactly one time the mocked method with mocked parameters after its behavior is changed during the test 1+ times?