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

时间:2016-04-29 16:47:59

标签: java mockito

设置如下:

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

2 个答案:

答案 0 :(得分:4)

正如我在评论中所提到的,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认为这些值是要检查的。
  4. 幕后

    拨打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不会认为您正在验证{{ 1}},但你不知何故认为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));
    

答案 1 :(得分:1)

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

  

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