New issue 212 by szczepiq: When parent class of the mock is not public then
mock misbehaves
http://code.google.com/p/mockito/issues/detail?id=212
When parent class of the mock is not public then mock misbehaves
Comment #1 on issue 212 by szczepiq: When parent class of the mock is not
public then mock misbehaves
http://code.google.com/p/mockito/issues/detail?id=212
Reported at the mailing list. See test ParentClassNotPublicVeryWeirdBugTest
for code sample. Remove @Ignore & run tests via ant to reproduce it.
The problem can be fixed very easily by making the parent class of the mock
public. Also this problem should be quite rare.
Fixing it requires digging into cglib and figuring out why the methods are
not intercepted in this scenario.
Patch from the community would be very welcome :)
Comment #2 on issue 212 by szczepiq: When parent class of the mock is not
public then mock misbehaves
http://code.google.com/p/mockito/issues/detail?id=212
(No comment was entered for this change.)
I recently had this problem, and I got stuck on it for quite a bit, since
the error message wasn't very clear.
I got a message like this:
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Invalid use of argument matchers!
0 matchers expected, 4 recorded.
This exception may occur if matchers are combined with raw values:
//incorrect:
someMethod(anyObject(), "raw String");
When using matchers, all arguments have to be provided by matchers.
For example:
//correct:
someMethod(anyObject(), eq("String by matcher"));
For more info see javadoc for Matchers class.
at
com.googlecode.afdbaccess.cache.TransactionBase.executeQuery(TransactionBase.java:55)
at
com.googlecode.afdbaccess.cache.TransactionNoCommit.executeQuery(TransactionNoCommit.java:1)
at
com.googlecode.afdbaccess.web.common.login.UserInfoFetcherTest.mockTransaction(UserInfoFetcherTest.java:93)
at
com.googlecode.afdbaccess.web.common.login.UserInfoFetcherTest.testUserNotFound(UserInfoFetcherTest.java:40)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at
org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
at
org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at
org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
at
org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at
org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76)
at
org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
at
org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37)
at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62)
at
org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
at
org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
It took some experiments to figure out that it started working if I made
the base class public instead of default/package-private. :)
If this defect can't be fixed easily, can the error message be made more
clear?
I made a smaller Eclipse project to try to recreate the problem, but in
that smaller case, I instead get this error message:
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Misplaced argument matcher detected here:
-> at
nonpublicsuperclass.SubClassTest.testTheInheritedMethodThroughSubClassWithMatchers(SubClassTest.java:32)
You cannot use argument matchers outside of verification or stubbing.
Examples of correct usage of argument matchers:
when(mock.get(anyInt())).thenReturn(null);
doThrow(new RuntimeException()).when(mock).someVoidMethod(anyObject());
verify(mock).someMethod(contains("foo"))
Also, this error might show up because you use argument matchers with
methods that cannot be mocked.
Following methods *cannot* be stubbed/verified:
final/private/equals()/hashCode().
at
nonpublicsuperclass.SubClassTest.testTheInheritedMethodThroughSubClassWithMatchers(SubClassTest.java:32)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at
org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
at
org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at
org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
at
org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at
org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76)
at
org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
at
org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37)
at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62)
at
org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
at
org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
When I don't use matchers, I get:
org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.
For example:
when(mock.getArticles()).thenReturn(articles);
Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods.
Those methods *cannot* be stubbed/verified.
2. inside when() you don't call method on mock but on some other object.
at
nonpublicsuperclass.SubClassTest.testTheInheritedMethodThroughSubClass(SubClassTest.java:27)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at
org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
at
org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at
org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
at
org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at
org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76)
at
org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
at
org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37)
at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62)
at
org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
at
org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
I've attached the Eclipse project I used to get the later two stack traces.
The first stacktrace I'm only able to get with my full code.
Attachments:
Mockito-inheritedmethodfail.zip 1.2 MB
Very nice detailed report ;) THANKS!
Since mocking classes that are not public does not work I guess we should
make it fail fast at mock creation time.
Comment #5 on issue 212 by szczepiq: When parent class of the mock is not
public then mock misbehaves
http://code.google.com/p/mockito/issues/detail?id=212
This problem is related to bridge methods...
Issue 258 has been merged into this issue.
Unfortunately, it seems that there is a bug in java. I filed the report
here: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7011164
Issue 274 has been merged into this issue.
Another example: we just ran into this trying to test SSHClient setup in
the nice sshj [1] library (great alternative to JSch!). SSHClient [2] (the
main entry point) inherits from SocketClient [3] which, unfortunately from
the point of view of this bug, is package visible.
The connect() family of methods thus cannot be stubbed out in a test,
meaning that writing unit tests without *actual* hosts to connect to is
hard :-(
[1] https://github.com/shikhar/sshj
[2]
https://github.com/shikhar/sshj/blob/master/src/main/java/net/schmizz/sshj/SSHClient.java
[3]
https://github.com/shikhar/sshj/blob/master/src/main/java/net/schmizz/sshj/SocketClient.java
The workaround we use in our case: define a subclass of the class to test
that adds delegate methods for those methods we need to stub. E.g.
public class MockitoFriendlySSHClient extends SSHClient {
// we need to stub this
@Override
public void connect(String hostname, int port) throws IOException {
super.connect(hostname, port);
}
}
and in the test
SSHClient client = mock(MockitoFriendlySSHClient.class);
Of course, this is a little maintenance-intensive if the base class
changes, but it's at least compile-safe.
Given that the Java guys responded with an evaluation of "Not bug" - is
this something that can be worked around within Mockito, rather than inside
our test code?
Maybe maybe not, it might require tremendous effort, maybe solving this
case would break other legitimate cases. We are interested for some time to
experience with other bytecode engine, they might help to solve this case.
Speaking for myself, I would not bet on a fix of this bug the near future.
@sharedocs1 - I gather that the workaround with making the parent public is
not possible in your scenario?
This problem cannot be completely fixed. With some minor changes to cglib
we might have a workaround for most cases, let's say 90%. That might be
good enough but I would need to pair with someone on that as it's kinda
tricky stuff and makes me uncomfortable doing it.
> I gather that the workaround with making the parent public is not
> possible in your scenario?
No, unfortunately not - the parent is part of a 3rd-party library. But
we're pretty happy with the workaround we found (which is essentially
creating a "new" public parent using a subclass that we *do* control).