Java 如何覆盖 Mockito 模拟上的默认答案?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22563208/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 16:30:45  来源:igfitidea点击:

How do I override default Answers on a Mockito mock?

javamockito

提问by Dancrumb

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 myServiceshould answer in a standard manner. However calls to mobileMethod(which is public) should be answered in a specific way.

我的意图是所有对被嘲笑者的调用都myService应该以标准方式回答。但是mobileMethod,应以特定方式回答对(公开的)的调用。

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.

我发现的是,当我到达线路以添加对 调用的答案mobileMethod而不是附加 时MobileServiceAnswer,Java 实际上是在调用myService.mobileMethod,这会导致 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:

这是我Answer的:

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;
    }
}

采纳答案by Jeff Bowman

Two unrelated surprises are causing this problem together:

两个不相关的意外一起导致了这个问题:

During the stub, Java calls your real answer, and tries to call setStateon 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 mobileMethodimmediately precedes a call to when. It hasn't gotten there yet.

在存根期间,Java 调用您的真实答案,并尝试调用setState您的基于匹配器(空)的参数。基于 Java 评估顺序,这是有道理的:Mockito 调用您的答案就好像它是被测系统调用您的答案一样,因为 Mockito 无法知道mobileMethodwhen. 它还没有到达那里。

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:

答案是使用“doVerb”的方法,比如doAnswerdoReturndoThrow,我喜欢称之为“尤达语法”。因为这些包含when(object).method()而不是when(object.method()),Mockito 有机会取消您先前设定的期望,并且永远不会触发您的原始答案。它看起来像这样:

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 commandand contextduring the stub without throwing an exception, which can introduce subtle testing gaps.

值得注意的是,异常是您的覆盖不起作用的唯一原因。在正常情况下,“when-thenVerb”绝对可以覆盖,并且会回溯到前一个动作,以免抛出像.thenReturn(...).thenThrow(...). 另外值得一提的是,when(mobileMethod(command, context))会改变commandcontext没有抛出异常,它可以引入微妙的测试空白存根中。

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.

一些开发人员甚至更喜欢“doVerb-when”语法而不是“when-thenVerb”语法,因为它具有从不调用其他模拟的良好行为。欢迎你得出同样的结论——“doVerb”可以完成“when-thenVerb”所做的一切,但在覆盖模拟和间谍中的行为时使用更安全。我自己更喜欢“when”语法——读起来好一点,而且它会检查返回值的类型——只要你记住有时“doVerb”是到达你需要去的地方的唯一方法。

回答by slim

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.

因此,请使用调试器逐步执行您的测试用例,以找出您正在做的与这个简单案例不同的事情。