我可以在实例化时设置一个带有期望(验证)的 Mockito 模拟吗?

Can I set up a Mockito mock with expectations (verify) at instantiation time?

在 Mockito 中验证预期方法是否具有 运行 通常是这样的:

when(mockFoo.someMethod()).thenReturn(someValue);
// run test
verify(mockFoo, times(n)).someMethod();

有什么方法可以让我在创建模拟时指定验证。在类似 EasyMock 的东西中我可以做:

mockFoo = EasyMock.createMock(Foo.class);
EasyMock.expect(mockFoo.someMethod()).times(n).andReturn(someValue);
// then run test

我的用例是我有一个经常重用的测试依赖项,我想模拟它(doesFooMethodAndReturnBar5Times 模拟),但是使用 Mockito 我没有办法对其执行验证。

更新:这个答案发生了重大变化,因为 Mockito 2 中提供了严格的模拟,而 Mockito 3 中默认情况下将提供对严格存根的强制执行。使用 strictlenient 模式来配置这些模拟和存根并查看 mockito issue 769 文档和进度。

在 Mockito 2 之前,这不是 Mockito 可以轻易做到的事情。 EasyMock 的默认严格模拟确保 (1) 意外交互立即失败,以及 (2) 所有预期交互发生;除了 verifyNoMoreInteractions (它不会立即失败,而是在测试结束时失败)之外,Mockito 没有任何设置。这是 philosophical design decision on Mockito's part, and see this thread Mockito 发起人进一步讨论的地方。


事实上,Mockito 的 when 语法 取决于 它允许意外交互,因为意外交互告诉 Mockito 调用了哪个方法:

when(mockFoo.someMethod()).thenReturn(someValue);
//   ^^^^^^^^^^^^^^^^^^^^  Java calls this first to get an argument for when,
//                         which is how Mockito knows which method to stub:
//                         it's always the last one called.

容忍意外调用的 EasyMock 模拟被命名为 "nice mocks"; Mockito 的一大卖点是模拟默认情况下很好,因此它们通常可以容忍与被测试行为无关的调用。这确实使调试变得有点困难,因为 Mockito 不会像 EasyMock 那样在意外交互时立即失败,但它也使测试不那么脆弱——更有可能的是安全更改仍然会破坏测试,因为 EasyMock 模拟接到了一个意想不到的电话。 在继续之前,请与您的团队核实他们是否会对您在此处的选择感到满意:对于 Mockito 和损坏的,严格的模拟语义相对较新假设可能会有与框架变化一样大的交易。 (至此,在看到替代方案之后,他们可能会让您最终使用 EasyMock!)


要在 Mockito 2+ 中使用严格模拟, 请参阅 syntax and libraries in Mockito issue 769。这可能是从 Mockito 1.x.

升级的一个很好的理由

要在 Mockito 中模拟严格的模拟 1.x,您需要设置一个未通过测试的默认答案,并且仅使用 doVerb 方法(doAnswerdoReturndoThrow 等)。此语法为 Mockito 提供了停用存根行为所需的警告。要创建默认答案,您可以为单个方法(首选)或为单个模拟上的所有方法设置行为。

public class ThrowingAnswer extends Answer<Object> {
  @Override public Object answer(InvocationOnMock invocation) {
    throw new AssertionError("Unexpected invocation: " + invocation);
  }
}

// apply to the entire object:
YourObject yourObject = Mockito.mock(YourObject.class, new ThrowingAnswer());

// or per-method:
YourObject yourObject = Mockito.mock(YourObject.class);
doAnswer(new ThrowingAnswer()).when(yourObject).scaryMethod(any());

Mockito 将始终在最后定义的匹配链上进行 return 行为,并且仅在没有链匹配时才使用默认答案,因此您应该能够使用 [=16= 定义任意数量的链] 方法来覆盖该行为。