I have the following code:
private MyService myService;
@Before
public void setDependencies() {
myService = Mockito.mock(MyService.class, new StandardServiceAnswer());
Mockito.when(myService.mobileMethod(Mockito.any(MobileCommand.class), Mockito.any(Context.class)))
.thenAnswer(new MobileServiceAnswer());
}
My intention is that all calls to the mocked myService
should answer in a standard manner. However calls to mobileMethod
(which is public) should be answered in a specific way.
What I'm finding is that, when I get to the line to add an answer to calls to mobileMethod
, rather than attaching the MobileServiceAnswer
, Java is actually invoking myService.mobileMethod
, which results in an NPE.
Is this possible? It would seem like it should be possible to override a default answer. If it is possible, what is the correct way to do it?
Here are my Answer
s:
private class StandardServiceAnswer implements Answer<Result> {
public Result answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
Command command = (Command) args[0];
command.setState(State.TRY);
Result result = new Result();
result.setState(State.TRY);
return result;
}
}
private class MobileServiceAnswer implements Answer<MobileResult> {
public MobileResult answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
MobileCommand command = (MobileCommand) args[0];
command.setState(State.TRY);
MobileResult result = new MobileResult();
result.setState(State.TRY);
return result;
}
}
Two unrelated surprises are causing this problem together:
Mockito.any(Class)
doesn't actually return an object of that class . It returns null
and stashes a "disregard the parameter and accept anything" matcher on a secret internal matcher stack called ArgumentMatcherStorage . That argument value will actually be null, but in most cases you won't see it.
The statement when(foo.bar()).thenReturn(baz)
actually calls foo.bar()
, always. Typically this has no side effects—especially if you're stubbing its first chain of actions—so you don't notice it.
During the stub, Java calls your real answer, and tries to call setState
on your matcher-based (null) argument. Based on Java evaluation order, this makes sense: Mockito calls your answer as if it were the system under test calling your answer, because there's no way for Mockito to know that the call to mobileMethod
immediately precedes a call to when
. It hasn't gotten there yet.
The answer is to use the "doVerb" methods, such as doAnswer
, doReturn
, and doThrow
, which I like to call "Yoda syntax". Because these contain when(object).method()
instead of when(object.method())
, Mockito has a chance to deactivate your previously-set expectations, and your original answer is never triggered. It would look like this:
MyService myService = Mockito.mock(MyService.class, new StandardServiceAnswer());
Mockito.doAnswer(new MobileServiceAnswer())
.when(myService).mobileMethod(
Mockito.any(MobileCommand.class), Mockito.any(Context.class));
It's worth noting that the exception is the only reason that your override didn't work . Under normal circumstances "when-thenVerb" is absolutely fine for overriding, and will backtrack over the previous action so as not to throw off consecutive actions like .thenReturn(...).thenThrow(...)
. It's also worth noting that when(mobileMethod(command, context))
would have changed command
and context
during the stub without throwing an exception, which can introduce subtle testing gaps.
Some developers go so far as to prefer the "doVerb-when" syntax over the "when-thenVerb" syntax at all times, because it has that nice behavior of never calling the other mock. You're welcome to come to the same conclusion—"doVerb" does everything "when-thenVerb" does, but is safer to use when overriding behavior in mocks and spies. I prefer "when" syntax myself—it's a little nicer to read, and it does type-check return values—as long as you remember that sometimes "doVerb" is the only way to get where you need to go.
What you want to do is valid, and when I do it it works:
private Properties props;
@Before
public void setUp() {
props = mock(Properties.class, new Answer<String>() {
@Override
public String answer(InvocationOnMock invocation) throws Throwable {
return "foo";
}
} );
when(props.get("override")).thenAnswer(new Answer<String>() {
@Override
public String answer(InvocationOnMock invocation) throws Throwable {
return "bar";
}
} );
}
@Test
public void test() {
assertEquals("foo", props.get("no override"));
assertEquals("bar", props.get("override"));
}
So step through the execution of your testcase with a debugger to find out what you're doing that's different from this simple case.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.