Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I override default Answers on a Mockito mock?

Tags:

java

mockito

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 Answers:

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;
    }
}
like image 814
Dancrumb Avatar asked Mar 21 '14 15:03

Dancrumb


People also ask

Why doesn't Mockito know when to call mobilemethod?

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.

What is the default behavior of Mockito?

We should start with default Mockito behavior which is RETURNS_DEFAULTS You are probably already familiar with it because that’s how Mockito works out of the box. What you might’ve missed is what values will be returned for primitives (and their boxed values). It looks following:

Is there a way to get Yoda syntax with Mockito?

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.

How do I create a mock method with a mocksettings argument?

A MockSettings object is instantiated by a factory method as follows: That setting object will be used in the creation of a new mock: Similar to the preceding section, we will invoke the add method of a MyList instance and verify that a mock method with a MockSettings argument works as it is meant to by using the following code snippet:


1 Answers

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.

like image 116
Jeff Bowman Avatar answered Oct 20 '22 01:10

Jeff Bowman