简体   繁体   English

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

[英]How do I override default Answers on a Mockito mock?

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. 我的意图是对模拟myService所有调用都应该以标准方式回答。 However calls to mobileMethod (which is public) should be answered in a specific way. 但是,应以特定方式回答对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 Answer s: 这是我的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;
    }
}

Two unrelated surprises are causing this problem together: 两个无关的惊喜一起导致了这个问题:

  • Mockito.any(Class) doesn't actually return an object of that class . Mockito.any(Class) 实际上并不返回该类的对象 It returns null and stashes a "disregard the parameter and accept anything" matcher on a secret internal matcher stack called ArgumentMatcherStorage . 它返回null并在一个名为ArgumentMatcherStorage秘密内部匹配器堆栈隐藏 “忽略参数并接受任何东西”匹配 That argument value will actually be null, but in most cases you won't see it. 该参数值实际上将为null,但在大多数情况下,您将看不到它。

  • The statement when(foo.bar()).thenReturn(baz) actually calls foo.bar() , always. when(foo.bar()).thenReturn(baz)实际上总是调用foo.bar() when(foo.bar()).thenReturn(baz)的语句。 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. 在存根期间,Java会调用您的真实答案,并尝试在基于匹配器的(null)参数上调用setState 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 . 基于Java的计算顺序,这是有道理的:调用的Mockito你的答案好像是在测试调用你的答案的系统,因为没有办法来的Mockito知道调用mobileMethod立即先于通话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". 答案是使用“doVerb”方法,例如doAnswerdoReturndoThrow ,我喜欢称之为“Yoda语法”。 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. 因为它们包含when(object).method()而不是when(object.method()) ,所以Mockito有机会停用你之前设定的期望,并且你的原始答案永远不会被触发。 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(...) . 在正常情况下,“when-thenVerb”绝对可以覆盖,并且会回溯前一个动作,以免甩掉像.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. 值得注意的是, 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. 有些开发人员甚至比“when-thenVerb”语法更喜欢“doVerb-when”语法,因为它具有从不调用其他模拟的好行为。 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. 我们欢迎您得出相同的结论 - “doVerb”会执行“when-thenVerb”所做的一切,但在模拟和间谍中覆盖行为时更安全。 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. 我更喜欢“when”语法本身 - 它读起来更好一些,并且它会进行类型检查返回值 - 只要你记得有时候“doVerb”是获得你需要去的地方的唯一方法。

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. 因此,使用调试器逐步执行测试用例,以找出您正在做的与此简单案例不同的内容。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM