【问题标题】:Cannot use mocked function calls in parameters of verify: too many invocations无法在验证参数中使用模拟函数调用:调用过多
【发布时间】:2016-04-29 16:47:59
【问题描述】:

设置如下:

//call doA a bunch of times, call doB once using some value that depends on doA()
verify(mockedThing).doB(eq(mockedThing.doA())); //removing eq() changes nothing

显然,doA() 被配置为返回一些值,而mockedThing 确实被模拟了。结果是:mockito 抱怨我打电话给doA(这里强调:不是doB!)太频繁了,而且它希望它只被调用一次!

以下更改有效:

int result = mockedThing.doA() 
verify(mockedThing).doB(eq(result));

我的问题很简单:这里发生了什么?为什么 Mockito 验证对我传递给函数的参数的调用,而不是对函数本身的调用?

【问题讨论】:

  • 你想在这里测试什么对象/类?您似乎只在这里调用模拟方法,没有实际的类
  • 我做了一个简单的实现来简化问题,突出问题。该测试基本上只是对真实对象的doSomething() 的一些调用,它触发了模拟的doA()(不止一次)和doB(int)(仅一次)函数。特别是,doSomething() 至少包含两行:mockedThing.doA();mockedThing.doB(mockedThing.doA());。这足以重现行为。
  • 很多 Mockito 都基于最后调用的函数,并且对 mock 的调用以与您尝试使用的语法不兼容的方式隐式检查状态。 I wrote a bit more on this answer here.

标签: java mockito


【解决方案1】:

正如我在评论中提到的,Mockito 实际上是以不直观的方式有状态的;很多时候,存根或验证的方法只是“最后调用的方法”,主要是因为像verify(foo).doA() 这样的语法实际上调用了doA,而不是将方法doA 的反射引用传递给Mockito。这与在存根或验证过程中调用相同模拟的语法完全不兼容。

我有written about this before with regard to Matchers,在存根期间也有同样的问题。查看源代码,您可以看到验证存在同样的问题,至少在同一个 mock 上调用方法时是这样。

简而言之,验证实际上是一个三阶段的过程:

  1. 致电verify(mockedThing)
  2. 如有必要,按顺序调用匹配器。不要在mockedThing 上调用任何方法。
  3. mockedThing 上调用您正在验证的方法,如果您不使用匹配器,则使用实际参数值,如果您使用匹配器,则使用虚拟(忽略)参数值。由于 Mockito 在后台跟踪匹配器堆栈,因此匹配器方法可以返回 0null,而 Mockito 不会认为这些是要检查的值。

幕后

调用verify实际上只是set a flag and return the exact same mock

public <T> T verify(T mock, VerificationMode mode) {
  // [catch errors]
  mockingProgress.verificationStarted(new MockAwareVerificationMode(mock, mode));
  return mock;
}

然后,inside the handler that handles all mock invocations,Mockito 在验证开始后第一次调用模拟时开始验证:

public Object handle(Invocation invocation) throws Throwable {
  // [detect doAnswer stubbing]
  VerificationMode verificationMode = mockingProgress.pullVerificationMode();
  // [check Matcher state]

  // if verificationMode is not null then someone is doing verify()
  if (verificationMode != null) {
    // We need to check if verification was started on the correct mock
    // - see VerifyingWithAnExtraCallToADifferentMockTest (bug 138)
    if (((MockAwareVerificationMode) verificationMode).getMock() == invocation.getMock()) {
      VerificationDataImpl data = createVerificationData(invocationContainerImpl, invocationMatcher);
      verificationMode.verify(data);
      return null;
    } else {
      // this means there is an invocation on a different mock. Re-adding verification mode
      // - see VerifyingWithAnExtraCallToADifferentMockTest (bug 138)
      mockingProgress.verificationStarted(verificationMode);
    }
  }

  // [prepare invocation for stubbing]
}

因此,如果您与 mock 进行交互只是为了获取参数值,Mockito 将假定您实际上是在调用该方法进行验证。请注意,如果调用稍有不同,例如 verify(mockedThing).doB(eq(5), eq(mockedThing.doA())); 带有额外的 eq(5),您将收到有关滥用匹配器的不同错误消息 - 特别是因为 Mockito 不只是认为您正在验证 doA,而是你以某种方式认为doA 需要争论。

后果

您的代码不起作用:

// DOESN'T WORK
verify(mockedThing).doB(eq(mockedThing.doA()));
// BECAUSE IT BEHAVES THE SAME AS
verify(mockedThing).doA();

但提取它确实有效:

// WORKS, though it makes an extra call to doA
Value value = mockedThing.doA();
verify(mockedThing).doB(eq(value));

这也有效,并且展示了幕后发生的事情,但永远不要在真正的测试中写这个

// WORKS BUT DON'T EVER ACTUALLY DO THIS
Value value = mockedThing.doA();
verify(mockedThing);
eq(value);
mockedThing.doB(8675309 /* dummy value ignored because of matcher */);

【讨论】:

    【解决方案2】:

    Jeff Bowman 的回答有助于解释发生了什么。

    很多 Mockito 都基于最后调用的函数,并且对 mock 的调用以与您尝试使用的语法不兼容的方式隐式检查状态。 I wrote a bit more on this answer here. – Jeff Bowman 昨天

    【讨论】:

    • 很高兴这有帮助!抱歉,我没有时间发布深入的答案(我在手机上);如果您好奇,我添加了一个更详细的信息。干杯!
    猜你喜欢
    • 2019-10-17
    • 2014-09-26
    • 2013-03-28
    • 2022-08-23
    • 1970-01-01
    • 1970-01-01
    • 2018-08-26
    • 2013-01-21
    • 2019-12-02
    相关资源
    最近更新 更多