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
How do I override default Answers on a Mockito mock?
提问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 myService
should 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 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;
}
}
采纳答案by Jeff Bowman
Two unrelated surprises are causing this problem together:
两个不相关的意外一起导致了这个问题:
Mockito.any(Class)
doesn't actually return an object of that class. It returnsnull
and stashes a "disregard the parameter and accept anything" matcher on a secret internal matcher stack called ArgumentMatcherStorage. That argument value will actually be null, but in most cases you won't see it.The statement
when(foo.bar()).thenReturn(baz)
actually callsfoo.bar()
, always. Typically this has no side effects—especially if you're stubbing its first chain of actions—so you don't notice it.
Mockito.any(Class)
实际上并不返回该类的对象。它null
在名为 ArgumentMatcherStorage的秘密内部匹配器堆栈上返回并隐藏“忽略参数并接受任何内容”匹配器。该参数值实际上将为空,但在大多数情况下您不会看到它。该语句
when(foo.bar()).thenReturn(baz)
实际上foo.bar()
始终调用。通常这没有副作用——尤其是当你在阻止它的第一条动作链时——所以你不会注意到它。
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.
在存根期间,Java 调用您的真实答案,并尝试调用setState
您的基于匹配器(空)的参数。基于 Java 评估顺序,这是有道理的:Mockito 调用您的答案就好像它是被测系统调用您的答案一样,因为 Mockito 无法知道mobileMethod
对when
. 它还没有到达那里。
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”的方法,比如doAnswer
,doReturn
和doThrow
,我喜欢称之为“尤达语法”。因为这些包含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 command
and context
during the stub without throwing an exception, which can introduce subtle testing gaps.
值得注意的是,异常是您的覆盖不起作用的唯一原因。在正常情况下,“when-thenVerb”绝对可以覆盖,并且会回溯到前一个动作,以免抛出像.thenReturn(...).thenThrow(...)
. 另外值得一提的是,when(mobileMethod(command, context))
会改变command
并context
没有抛出异常,它可以引入微妙的测试空白存根中。
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.
因此,请使用调试器逐步执行您的测试用例,以找出您正在做的与这个简单案例不同的事情。