How do I verify the number of invocations of overl

2019-03-01 07:40发布

问题:

How do I check if bar(Alpha, Baz) called bar(Xray, Baz) using Mockito - without actually calling the later, given my MCVE class Foo:

public class Foo {
    public String bar(Xray xray, Baz baz) {
        return "Xray";
    }

    public String bar(Zulu zulu, Baz baz) {
        return "Zulu";
    }

    public String bar(Alpha alpha, Baz baz) {
        if(alpha.get() instanceof Xray) {
            return bar((Xray)alpha.get(), baz);
        } else if(alpha.get() instanceof Zulu) {
            return bar((Zulu)alpha.get(), baz);
        } else {
            return null;
        }
    }
}

Currently, I have roughly the following Mockito-enhanced JUnit test:

@Test
public void testBar_callsBarWithXray() {
    Baz baz = new Baz(); //POJOs
    Alpha alpha = new Alpha();
    alpha.set(new Xray());

    Foo foo = new Foo();
    Foo stub = spy(foo); // Spying on Foo, as I want to call the real bar(Alpha, Baz)
    // Preventing bar(Xray, Baz) from being called by providing behavior/stub
    when(stub.bar(any(Xray.class), any(Baz.class))).thenReturn("ok");
    // Calling the real "parent" method
    stub.bar(alpha, baz);
    // Testing if bar(Xray, Baz) was called by bar(Alpha, Baz)
    verify(stub, times(1)).bar(any(Xray.class), any(Baz.class));
}

And it fails:

org.mockito.exceptions.verification.TooManyActualInvocations:
foo.bar(
    <any>,
    <any> ); 
Wanted 1 time:
-> at FooTest.testBar_callsBarWithXray(FooTest.java:14) 
But was 2 times. Undesired invocation:
-> at FooTest.testBar_callsBarWithXray(FooTest.java:12)

I suppose it's because any(Class.class) matches anything, and doesn't do type checking... So, from Mockito's point of view, I'm really calling bar() twice: in Foo and in FooTest (on line 12).

What do I have to do to make the test do what I actually want: ensure bar(Xray, Baz) was called when I call bar(Alpha, baz) (while at the same time intercepting the call to bar(Xray, Baz))?

Using the eq() Matcher (when(stub.bar(eq(Xray.class), any(Baz.class))...) causes a compilation error, Cannot resolve method 'bar(Class<Xray>, Baz)' - but then, I probably shouldn't use it that way anyway (eq(xray) would probably be more like it)...

Also, on a related note, if I try to make the overloads of bar(Alpha, Baz) private, I get a compilation error for FooTest, stating:

Error:(10, 12) java: bar(Xray,Baz) has private access in Foo

Is there a way to get round that using PowerMockito? How? (Obviously, I only want to count the calls to bar(Xray, Baz) - counting all calls to all overloads of bar() is out of the question...)

Just using eq(xray) instead of any(Xray.class) on lines 10 and 14 of the test does the trick - but I'm not really interested in what (specific) Xray object is passed in as an argument to bar(), as long as it's any Xray object...

Update: I've posted a separate question for the "PowerMockito and private methods" part of the discussion, and figured out how to make this work for public methods; cf. my comment below.

回答1:

You don't need to do this.

The reason this is driving you nuts is that you aren't using Mockito the way it is intended to be used. It can be used this way, but it should not be.

You don't need to test that the right method was called, you need to test that the behavior of your class is correct. What does that mean?

  1. You want to check that "Xray" was returned.

    String result = foo.bar(alpha, baz);
    assertEquals("Xray", result);
    
  2. You want to check that Alpha.get was called. If anything, this means that Alpha should be the stub, not Foo.

    Alpha alpha = spy(new Alpha());
    String result = foo.bar(alpha, baz);
    verify(alpha).get();
    

The bottom line is this: You have your System Under Test, in this case Foo. That class should be a real class. Your mocks and stubs should be the other objects that interact with Foo; verify that the interactions do the correct things.

In other words, you shouldn't be testing how Foo works, you should be testing that Foo does what it's supposed to do; that the results are correct.