问题
Consider you want to mock an interface using Mockito
containing the following method signatures:
public void doThis(Object o);
public void doThis(Object... o)
I need to verify that doThis(Object o)
(and not the other method) has been invoked exactly one time.
First I thought that the following line would do the trick:
verify(mock, times(1)).doThis(anyObject());
However, as this seems to work on Windows, it doesn't on Linux because in this environment, a call to of the other doThis
method is expected.
This is caused because the anyObject()
argument seems to match both method signatures and one is chosen in a more or less unpredictable way.
How can I enforce that Mockito always chooses doThis(Object o)
for verification?
回答1:
This is not a mockito issue.
During further investigating, I realized that the actual method is chosen at compile-time (JLS §15.12.2). So basically the class files differed between windows and linux which caused different mockito behavior.
The interface is discouraged (see Effective Java, 2nd Edition, Item 42
).
I changed it to match the following:
public void doThis(Object o);
public void doThis(Object firstObj, Object... others)
With this change the expected (first) method will always be chosen.
One thing is still left:
Why does the java compiler on windows (eclipse compiler) produce a different output than Oracle JDK javac on Linux?
This might be a bug in ECJ because I would expect that the java language spec is pretty strict here.
回答2:
I agree to most from the other answer, just one part hasn't been answered yet: why the difference in compilers?
At a closer look this is not a difference between ecj and javac, but between different versions of JLS:
- compiling at compliance 1.7 or less, both compilers select the first (single argument) method.
- compiling at compliance 1.8, both compilers select the second (varargs) method
Let's have a look at the produced bytecodes:
7: invokestatic #8 // Method anyObject:()Ljava/lang/Object;
10: checkcast #9 // class "[Ljava/lang/Object;"
13: invokevirtual #10 // Method doThis:([Ljava/lang/Object;)V
(both compilers also agree on these bytes)
This is to say: inference in Java 8 has become more powerful and is now able to infer the type argument for anyObject()
to Object[]
. This can be seen by the checkcast
instruction, which translated back into source code reads: (Object[])anyObject()
. This implies that also doThis(Object...)
can be invoked without varargs magic, but by passing a single argument of type Object[]
.
Now both methods are applicable in the same category ("applicable by fixed arity invocation") and search for the most specific among applicable methods selects the second method.
By contrast, Java 7 would allow invoking the second method only as a variable-arity invocation, but this is not even tried if a fixed-arity match is found.
The above also indicates how to make the program robust against the changes in JLS:
verify(mock, times(1)).doThis(Matchers.<Object>anyObject());
This will tell all compilers at all versions to select the first method, because now it will always see the argument of doThis()
as Object
-- if you really can't avoid this unhealthy overload, that is :)
来源:https://stackoverflow.com/questions/34265732/mockito-verifying-overloaded-methods-with-type-compatible-arguments