I'm using Mockito to creating unit tests for Java however I encountered a weird issue
Say I have an ObjectUnderTest
with a mainMethod
, and a dependency Dependency
mocked, with a method mockedMethod
:
class ObjectUnderTest {
private Dependency dependency;
public mainMethod() {
// some logic...
// now calling the `mockedMethod` with some retry config
// this is pseudocode but you get it
retryOn(RETRY_TIMES, SOME_INTERVAL,
() -> dependency.mockedMethod("mockedValue") != null, // do this each retry and returns when dependency returns non-null
() -> new SomeException("kaboom") // throw some exception after retry exhausted
);
// some other logic...
}
}
class MyTestClass {
// say Dependency has a method
// public ReturnValueDep mockedMethod(String)
private Dependency mockedDep;
private ObjectUnderTest testObj;
@BeforeEach
public void setUp() {
// set up mocks...
this.testObj = new ObjectUnderTest(mockedDep, // other params...
);
}
@Test
public void thisTestFailed() {
final OngoingStubbing<ReturnValueDep> stubbing = lenient().when(mockedDep.mockedMethod(eq("mockedValue")));
IntStream.rangeClosed(0, RETRY_TIMES).forEach((int i) -> stubbing.thenReturn(null));
stubbing.thenReturn(SOME_NON_NULL_VALUE);
assertThrows(SomeException.class, () -> testObj.mainMethod());
}
}
However I'm getting a Mockito error like this:
=> org.mockito.exceptions.base.MockitoException:
[java] Incorrect use of API detected here:
[java] -> at xxxxxx (redacted)
[java] You probably stored a reference to OngoingStubbing returned by when() and called stubbing methods like thenReturn() on this reference more than once.
[java] Examples of correct usage:
[java] when(mock.isOk()).thenReturn(true).thenReturn(false).thenThrow(exception);
[java] when(mock.isOk()).thenReturn(true, false).thenThrow(exception);
Any idea why this isn't allowed? This is so strange.
Adding lenient()
to it does not resolve the error. Seems like it cannot be bypassed.
The error message is quite clear about the problem: you cannot call .thenReturn
on the same OngoingStubbing
instance multiple times. So this is forbidden:
final OngoingStubbing<ReturnValueDep> stubbing = when(mockedDep.mockedMethod("mockedValue"));
stubbing.thenReturn(null);
stubbing.thenReturn(null);
stubbing.thenReturn(SOME_NON_NULL_VALUE);
Each .thenReturn
call returns a new OngoingStubbing
instance, which you can use to chain the stub calls. This works without problems:
final OngoingStubbing<ReturnValueDep> stubbing = when(mockedDep.mockedMethod("mockedValue"));
stubbing.thenReturn(null)
.thenReturn(null)
.thenReturn(SOME_NON_NULL_VALUE);
With a plain old loop and a non-final variable, this is trivial to write – simply reassign the stubbing
variable:
OngoingStubbing<ReturnValueDep> stubbing = .when(mockedDep.mockedMethod("mockedValue"));
for (int i = 0; i <= RETRY_TIMES; ++i) {
stubbing = stubbing.thenReturn(null);
}
stubbing.thenReturn(SOME_NON_NULL_VALUE);
If you need to use a stream, reducers come in handy:
OngoingStubbing<ReturnValueDep> stubbing = when(mockedDep.mockedMethod(eq("mockedValue")));
IntStream.rangeClosed(0, RETRY_TIMES)
.reduce(
when(mockedDep.mockedMethod("mockedValue")),
(stubbing, i) -> stubbing.thenReturn(null))
.thenReturn(SOME_NON_NULL_VALUE);