Simulation of Service using Mockito 2 leads to stubbing error
With strict stubs (the default behavior of Mockito) calling several when
s on the same method will reset that mock. The solution is to call when
once and have the logic in an Answer
:
@BeforeEach
public void beforeEach() {
when(testClass.booleanMethod(anyBoolean())).thenAnswer(invocationOnMock -> {
if ((boolean) invocationOnMock.getArguments()[0]) {
return 1;
}
return 2;
});
}
Alternatively, you can use lenient mocking, but that's not always a good idea - lenient mocking allows redundant stubbing, and makes it easier for you to make mistakes in your test, which may lead to unnoticed bugs in the "production" code:
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
public class MockitoExample {
Since Mockito 2.20 it is also possible, to add lenient() locally
@ExtendWith(MockitoExtension.class)
public class MockitoExample {
static abstract class TestClass {
public abstract int booleanMethod(boolean arg);
}
@Mock
TestClass testClass;
@BeforeEach
public void beforeEach() {
lenient().when(testClass.booleanMethod(eq(true))).thenReturn(1);
lenient().when(testClass.booleanMethod(eq(false))).thenReturn(2);
}
@Test
public void test() {
assertEquals(1,testClass.booleanMethod(true));
assertEquals(2,testClass.booleanMethod(false));
}
}
Mockito 1 and 2 don't have the same "strictness" level.
Besides by using Mockito 2 with JUnit 4 or 5 the default level will be still different.
To sum up :
3 levels of strictness :
LENIENT
: minimum strictnessWARN
: extra warnings emitted to the consoleSTRICT_STUBS
: ensures clean tests by throwing exception if potential misuse but may also produce some false positives.
Default effective level according to the APIs used :
- Mockito 1 :
LENIENT
- Mockito 2 with JUnit 4 :
WARN
- Mockito 2 with JUnit 5 (
MockitoExtension.class
) :STRICT_STUBS
- Mockito 3 : planned to be
STRICT_STUBS
.
More details
The actual Mockito documentation is very clear about that :
The Strictness
javadoc states :
Configures the "strictness" of Mockito during a mocking session.A session typically maps to a single test method invocation. Strictness drives cleaner tests and better productivity.The easiest way to leverage enhanced Strictness is usingMockito's JUnit support (MockitoRule or MockitoJUnitRunner).If you cannot use JUnit support MockitoSession is the way to go.
How strictness level influences the behavior of the test (mocking session)?
1.
Strictness.LENIENT
- no added behavior.The default of Mockito 1.x.Recommended only if you cannot use STRICT_STUBS nor WARN.2.
Strictness.WARN
- helps keeping tests clean and improves debuggability.Reports console warnings about unused stubsand stubbing argument mismatch (see org.mockito.quality.MockitoHint).The default behavior of Mockito 2.x when JUnitRule or MockitoJUnitRunner are used. Recommended if you cannot use STRICT_STUBS.3.
Strictness.STRICT_STUBS
- ensures clean tests, reduces test code duplication, improves debuggability.Best combination of flexibility and productivity. Highly recommended.Planned as default for Mockito v3.See STRICT_STUBS for the details.
But whatever the thrown exception associated to the message
"has following stubbing(s) with different arguments"
seems to be a excessively strict check. The exception message proves that in a some way :
However, there are legit scenarios when this exception generates false negative signal:
...
- stubbed method is intentionally invoked with different arguments by code under test
So forbidding it by default seems to be too much.
So if you use JUnit 5, as alternative to STRICT_STUBS
you could use WARNING
but you generally want to avoid LENIENT
that is too quiet.
In addition to MockitoExtension
, the mockito-junit-jupiter
library provides
@MockitoSettings
that may be used at the method level as well as at the class level.
Here is an example :
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
@ExtendWith(MockitoExtension.class)
public class FooTest {
@MockitoSettings(strictness = Strictness.WARN)
@Test
void foo() throws Exception {
List<String> strings = Mockito.mock(List.class);
Mockito.when(strings.add("a"))
.thenReturn(true);
Mockito.when(strings.add("b"))
.thenReturn(false);
}
@Test
void fooKo() throws Exception {
List<String> strings = Mockito.mock(List.class);
Mockito.when(strings.add("a"))
.thenReturn(true);
Mockito.when(strings.add("b"))
.thenReturn(false);
}
}
fooKo()
throws the misuse Mockito exception while foo()
is successful but provides helpful warnings :
[MockitoHint] FooTest (see javadoc for MockitoHint): [MockitoHint] 1. Unused -> at FooTest.foo(FooTest.java:19) [MockitoHint] 2. Unused -> at FooTest.foo(FooTest.java:21)
As other alternative you can also use Mockito.lenient()
very well described by
aschoerk to apply the lenient strictness for a specific invocation.
As well as you can set every mock invocations as lenient at the mock instantiation :
@Test
void foo() throws Exception {
List<String> strings = Mockito.mock(List.class, Mockito.withSettings()
.lenient());
....
}