简体   繁体   中英

How do I override default Answers on a Mockito mock?

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?

Update

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:

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM