In the unittest class show below, one of the tests fails and the other succeeds. Both tests create a CGLIB
object with an interceptor mock and attempt to verify that the interceptor is interacted with. The tests differ on the class that is dynamically subclassed with CGLIB. The test that succeeds subclasses a plain Java interface; the one that fails subclasses a dynamic subclass (created with Mockito
). Why is that first test failing?
Thanks.
public class ATest {
@Test
// This test fails.
public void cannotCallMethodOnMockWrapper() throws Throwable {
final Class<? extends Bar> c = Mockito.mock(Bar.class).getClass();
verifyInterceptorIsInvokedOnCallToObjectOfEnhanced(c);
}
@Test
// This test succeeds.
public void interceptorIsCalled() throws Throwable {
final Class<? extends Bar> c = Bar.class;
verifyInterceptorIsInvokedOnCallToObjectOfEnhanced(c);
}
private void verifyInterceptorIsInvokedOnCallToObjectOfEnhanced(
final Class<? extends Bar> c) throws Throwable {
final MethodInterceptor interceptor = Mockito.mock(
MethodInterceptor.class);
final Bar wrapper = (Bar) Enhancer.create(c, interceptor);
// Here is where the failing test chokes with exception:
// NoSuchMethodError
wrapper.foo();
verifyInterceptIsCalledOn(interceptor);
}
private void verifyInterceptIsCalledOn(
final MethodInterceptor interceptor) throws Throwable {
verify(interceptor).intercept(any(), any(Method.class),
any(Object[].class), any(MethodProxy.class));
}
public static interface Bar {
void foo();
}
}
UPDATE:
The stack trace of the failure
java.lang.NoSuchMethodError: java.lang.Object.foo()V
at ATest$Bar$$EnhancerByMockitoWithCGLIB$$2e1d601f.foo(<generated>)
at ATest.verifyInterceptorIsInvokedOnCallToObjectOfEnhanced(ATest.java:37)
at ATest.cannotCallMethodOnMockWrapper(ATest.java:19)
Also, as to whether a Mockito mock is a CGLIB-enhanced class, the test below (which fails) seems to suggest that it is not:
public class ATest {
@Test
public void mockitoMockIsCGLIBEnhanced() {
assertTrue(Enhancer.isEnhanced(Mockito.mock(Bar.class).getClass()));
}
public static interface Bar {
void foo();
}
}
Without a stacktrace I'll assume you have a net.sf.cglib.core.CodeGenerationException
probably with cause like InvocationTargetException
or ClassFormatError
,
And that happen because CGLIB can't enhance an already enhanced class that it created itself. As Mockito is using CGLIB internally on the JVM, you cannot enhance a Mockito class. you need to enhance the original class. Plus even if it was possible the callback is an instance you need pass to the generated class, so by creating your own enhancer you don't have the Mockito callback for the upper class. So the mockito mock functionality won't even work. Anyway, I'm diverging from the topic.
So basically you cannot enhance an enhanced class with CGLIB, if this issue is happening at runtime because you passed a mock, I believe you should have a code like this, check if it's enhanced, if yes use the superclass (the original class) and/or the interfaces :
public class CGLIBEnhancerEnhancer implements TypeEnhancer {
public void Object enhance(Object objectCandidateToEnhance, MethodInterceptor interceptor) {
Class classCandidateToEnhance = classCandidateToEnhance.getClass();
if(Enhancer.isEnhanced(classCandidateToEnhance)
|| Mockito.mockingDetails(objectCandidateToEnhance).isMock()) {
// safe with CGLIB (2.x) enhanced class
return (Bar) Enhancer.create(
classCandidateToEnhance.getSuperclass(),
classCandidateToEnhance.getInterfaces(),
interceptor
);
} else
return (Bar) Enhancer.create(classCandidateToEnhance, interceptor);
}
}
}
EDIT: I ran the given example and it indeed give me the CodeGenerationException
, that you can see at the end of this answer. Though maybe depending on the environment you can see a different stacktrace like the one you posted.
Given your exception I believe you might have an issue at runtime of how the double enhanced class is created. As the stacktrace suggest that the instance doesn't even have a foo method, it's like the object (which seems to be a real mockito mock) was generated from the wrong type. I would look at the API of the Enhancer to use correctly the type and interfaces to create the enhanced class, you could use a new instance of the Enhancer
or use static methods.
Also if you are proving the interceptor yourself, and that you want to execute the behavior of the given instance (mock or not), you should craft your interceptor such that it will hold a reference to the original object.
EDIT 2: Actually Mockito is repackaging/jarjar/inline CGLIB due to technical reasons, this means that net.sf.cglib.proxy.Enhancer
cannot detect a mockito mock. Instead one should use the newly introduced API in 1.9.5 Mockito.mockingDetails(instance).isMock()
to detect a mockito mock. Or use the repackaged/jarjared/inlined org.mockito.cglib.proxy.Enhancer
. In the end you'll need Mockito in your classpath to detect a Mockito mock.
Hope that helps
org.mockito.cglib.core.CodeGenerationException: java.lang.reflect.InvocationTargetException-->null
at org.mockito.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:238)
at org.mockito.cglib.proxy.Enhancer.createHelper(Enhancer.java:378)
at org.mockito.cglib.proxy.Enhancer.create(Enhancer.java:286)
at org.mockito.cglib.proxy.Enhancer.create(Enhancer.java:664)
at ATest.verifyInterceptorIsInvokedOnCallToObjectOfEnhanced(ATest.java:32)
at ATest.cannotCallMethodOnMockWrapper(ATest.java:18)
... removed
at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
... removed
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at org.mockito.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:385)
at org.mockito.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:220)
... 31 more
Caused by: java.lang.ClassFormatError: Duplicate method name&signature in class file ATest$Bar$$EnhancerByMockitoWithCGLIB$$58a2468b$$EnhancerByCGLIB$$9232d1df
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
... 37 more