1

在下面显示的 unittest 类中,一个测试失败,另一个测试成功。这两个测试都创建了一个CGLIB带有拦截器模拟的对象,并尝试验证拦截器是否与之交互。测试在使用 CGLIB 动态子类化的类上有所不同。成功的测试子类化一个普通的 Java 接口;失败的子类是一个动态子类(使用创建Mockito)。为什么第一次测试失败了?

谢谢。

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();
    }

}

更新:

失败的堆栈跟踪

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)

此外,关于 Mockito 模拟是否是 CGLIB 增强类,下面的测试(失败)似乎表明它不是:

public class ATest {

    @Test
    public void mockitoMockIsCGLIBEnhanced() {
        assertTrue(Enhancer.isEnhanced(Mockito.mock(Bar.class).getClass()));
    }

    public static interface Bar {
        void foo();
    }

}
4

1 回答 1

2

如果没有堆栈跟踪,我会假设你有一个net.sf.cglib.core.CodeGenerationException可能的原因,比如InvocationTargetExceptionor ClassFormatError

发生这种情况是因为 CGLIB 无法增强它自己创建的已经增强的类。由于 Mockito 在 JVM 内部使用 CGLIB,因此您无法增强 Mockito 类。你需要增强原来的类。另外,即使回调可能是您需要传递给生成的类的实例,所以通过创建自己的增强器,您没有上层类的 Mockito 回调。所以 mockito 模拟功能甚至不起作用。无论如何,我偏离了这个话题。

所以基本上你不能用 CGLIB 增强一个增强类,如果这个问题在运行时发生,因为你传递了一个模拟,我相信你应该有这样的代码,检查它是否被增强,如果是,使用超类(原始类)和/或接口:

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);
        }
    }
}

编辑:我运行了给定的示例,它确实给了我CodeGenerationException,你可以在这个答案的末尾看到。尽管可能取决于环境,但您可以看到与您发布的堆栈跟踪不同的堆栈跟踪。

鉴于您的例外情况,我相信您在运行时可能会遇到如何创建双重增强类的问题。由于堆栈跟踪表明该实例甚至没有 foo 方法,因此该对象(这似乎是一个真正的 mockito 模拟)是从错误的类型生成的。我会查看 Enhancer 的 API 以正确使用类型和接口来创建增强类,您可以使用的新实例Enhancer或使用静态方法。

此外,如果您自己证明拦截器,并且您想要执行给定实例的行为(无论是否模拟),您应该制作您的拦截器,使其包含对原始对象的引用。

编辑 2:实际上,由于技术原因,Mockito 正在重新打包/jarjar/内联 CGLIB,这意味着net.sf.cglib.proxy.Enhancer无法检测到 mockito 模拟。相反,应该使用 1.9.5 中新引入的 APIMockito.mockingDetails(instance).isMock()来检测 mockito mock。或者使用 repackaged/jarjared/inlined org.mockito.cglib.proxy.Enhancer。最后,您需要在类路径中使用 Mockito 来检测 Mockito 模拟。

希望有帮助

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
于 2012-12-12T10:26:02.677 回答