Cannot use mocked function calls in parameters of verify: too many invocations

后端 未结 2 1480
挽巷
挽巷 2021-01-21 16:31

The setup is as follows:

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


        
相关标签:
2条回答
  • 2021-01-21 17:01

    Jeff Bowman's answer helps explain what is going on.

    A lot of Mockito is based on the last-called function, and calls to mocks implicitly check state in ways that aren't compatible with the syntax you're trying to use. I wrote a bit more on this answer here. – Jeff Bowman yesterday

    0 讨论(0)
  • 2021-01-21 17:22

    As mentioned in my comment, Mockito is actually stateful in unintuitive ways; many times the method stubbed or verified is simply the "last called method", mostly because syntax like verify(foo).doA() actually calls doA rather than passing a reflective reference to the method doA into Mockito. This simply isn't compatible with syntax that calls the same mock in the middle of stubbing or verification.

    I've written about this before with regard to Matchers, which have the same problem during stubbing. Poking through the source code, you can see the same problem with verification, at least when calling a method on the same mock.

    In short, verification is actually a three phase process:

    1. Call verify(mockedThing).
    2. Call matchers, in order, if necessary. Do not call any methods on mockedThing.
    3. Call the method that you're verifying on mockedThing, with actual parameter values if you're not using matchers, and dummy (ignored) parameter values if you are using matchers. Since Mockito keeps track of the matchers stack in the background, matcher methods can return 0 or null without Mockito thinking those are values to check against.

    Under the covers

    Calls to verify actually just 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;
    }
    

    Then, inside the handler that handles all mock invocations, Mockito starts verification on the first call to the mock that happens once verification is started:

    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]
    }
    

    Therefore, if you interact with the mock just to get a parameter value, Mockito is going to assume that you're actually calling the method to verify. Note that if the call was slightly different, like verify(mockedThing).doB(eq(5), eq(mockedThing.doA())); with the extra eq(5), you'd get a different error message about misusing matchers—specifically because Mockito doesn't just think you're verifying doA, but that you somehow think doA takes an argument.

    Consequences

    Your code doesn't work:

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

    But extracting it does work:

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

    And this also works, and shows off what's happening under the covers, but don't ever write this in a real test:

    // 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 */);
    
    0 讨论(0)
提交回复
热议问题