22

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;
    }
}
Dancrumb
  • 26,597
  • 10
  • 74
  • 130
  • If you have this in your ```@Before``` method, then you shouldn't be able to use ```myService``` in the tests. Aren't you hiding an instance variable? Or is it just for demonstration? Also, how does StandardServiceAnswer look like? – NeplatnyUdaj Mar 21 '14 at 15:53
  • I've updated my sample to clear this up. `myService` is a private field in the test class – Dancrumb Mar 21 '14 at 16:24
  • Sorry for the confusion... it was for demonstration purposes. – Dancrumb Mar 21 '14 at 16:25
  • Add a short test method that demonstrates the problem. – slim Mar 21 '14 at 16:29

2 Answers2

43

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.

Community
  • 1
  • 1
Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
  • 1
    Thank you for this! I'd spent several hours debugging a related issue, and when the lightbulb finally went off of "oh, I wonder if it's because...", your post was the first thing I found that confirmed my suspicions, allowed me to write a small test to confirm it, and provided a simple solution. Thanks again! – scubbo May 17 '16 at 17:28
  • 1
    Thanks for the "Yoda syntax" phrase ;) – Hans Mar 16 '17 at 15:14
-2

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.

slim
  • 40,215
  • 13
  • 94
  • 127