不能在验证参数中使用模拟函数调用:调用次数过多

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了不能在验证参数中使用模拟函数调用:调用次数过多相关的知识,希望对你有一定的参考价值。

设置如下:

//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验证调用我传递给函数的参数而不是调用函数本身?

答案

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

我有written about this before with regard to Matchers,在存根期间也有同样的问题。通过源代码,你可以看到相同的验证问题,至少在同一个模拟器上调用方法时。

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

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

Under the covers

打电话给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]
}

因此,如果您只是为了获取参数值而与模拟进行交互,Mockito将假设您实际上正在调用该方法进行验证。请注意,如果调用略有不同,例如verify(mockedThing).doB(eq(5), eq(mockedThing.doA()));和额外的eq(5),你会得到一个关于滥用匹配器的不同错误消息 - 特别是因为Mockito不只是认为你正在验证doA,而是你不知怎的认为doA需要论点。

Consequences

您的代码不起作用:

// 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 */);
另一答案

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

很多Mockito都基于最后调用的函数,并且调用模拟以与您尝试使用的语法不兼容的方式隐式检查状态。 I wrote a bit more on this answer here. - 杰夫鲍曼昨天

以上是关于不能在验证参数中使用模拟函数调用:调用次数过多的主要内容,如果未能解决你的问题,请参考以下文章

递归与循环的区别

使用PowerMockito如何使用一组特定参数验证是否调用了构造函数

为啥这个正则表达式调用 substcont 次数过多?

如何测试在组件函数中调用的上下文函数的参数?

C++:调用无参数的构造函数为啥不加括号

线程栈溢出与线程属性