【问题标题】:How do I override default Answers on a Mockito mock?如何覆盖 Mockito 模拟上的默认答案?
【发布时间】:2014-03-21 15:41:58
【问题描述】:

我有以下代码:

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

我的意图是所有对模拟 myService 的调用都应该以标准方式回答。但是,应以特定方式接听对mobileMethod(公开)的呼叫。

我发现,当我在线添加对mobileMethod 的调用的答案时,Java 实际上是在调用myService.mobileMethod,而不是附加myService.mobileMethod,这会导致NPE。

这可能吗?似乎应该可以覆盖默认答案。 如果可能,正确的做法是什么?

更新

这是我的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;
    }
}

【问题讨论】:

  • 如果你的@Before 方法中有这个,那么你应该不能在测试中使用myService。你不是在隐藏一个实例变量吗?还是只是为了演示?另外,StandardServiceAnswer 是什么样子的?
  • 我已经更新了我的示例以清除它。 myService 是测试类中的私有字段
  • 抱歉给您带来了困惑……这是为了演示目的。
  • 添加一个演示问题的简短测试方法。

标签: java mockito


【解决方案1】:

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

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

答案是使用“doVerb”方法,例如doAnswerdoReturndoThrow,我喜欢称之为“Yoda语法”。因为这些包含 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));

值得注意的是,异常是您的覆盖不起作用的唯一原因。在正常情况下,“when-thenVerb”绝对可以覆盖,并且会回溯到之前的动作,以免抛出像.thenReturn(...).thenThrow(...)这样的连续动作。还值得注意的是,when(mobileMethod(command, context)) 在存根期间会更改 commandcontext 而不会引发异常,这可能会引入细微的测试差距。

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

【讨论】:

  • 谢谢你!我花了几个小时调试一个相关问题,当灯泡终于熄灭“哦,我想知道是不是因为......”时,你的帖子是我发现的第一件事,证实了我的怀疑,让我写了一个小测试确认一下,并提供了一个简单的解决方案。再次感谢!
  • 感谢“尤达语法”短语 ;)
【解决方案2】:

你想做的事情是有效的,当我这样做的时候它就起作用了:

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

因此,使用调试器逐步执行您的测试用例,以找出与这个简单用例不同的地方。

【讨论】:

    猜你喜欢
    • 2022-01-11
    • 1970-01-01
    • 2017-02-08
    • 1970-01-01
    • 1970-01-01
    • 2014-01-21
    • 2017-04-21
    • 2012-03-15
    • 2019-05-09
    相关资源
    最近更新 更多