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

2022-09-03 00:02:22

我有以下代码:

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

我的意图是,所有对被嘲笑者的呼吁都应该以标准的方式回答。但是,对(公开的)的呼叫应以特定方式应答。myServicemobileMethod

我发现,当我到达该行以添加对调用的应答而不是附加时,Java实际上是在调用,这会导致NPE。mobileMethodMobileServiceAnswermyService.mobileMethod

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

更新

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

答案 1

两个不相关的意外事件共同导致了这个问题:

  • Mockito.any(Class) 实际上不会返回该类的对象。它返回并隐藏一个名为ArspaceMatcherStorage的秘密内部匹配器堆栈上的“忽略参数并接受任何内容”匹配器。该参数值实际上将为 null,但在大多数情况下,您不会看到它。null

  • 该语句实际上总是调用 。通常,这没有副作用 - 特别是如果你正在存根它的第一个动作链 - 所以你不会注意到它。when(foo.bar()).thenReturn(baz)foo.bar()

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

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

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”可以完成“当-然后Verb”所做的一切,但是在嘲笑和间谍中覆盖行为时使用更安全。我自己更喜欢“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"));
}

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


推荐