从第三方课程中删除最终修饰符

时间:2018-01-22 16:02:44

标签: java junit javassist

我需要测试编写一个测试以下行的JUnit测试:

CSVRecord csvRecord = csvReader.readCsv(filename);

来自org.apache.commons.csv的CSVRecord是最后一堂课。如果我尝试使用EasyMock进行测试,我会收到以下错误:

java.lang.IllegalArgumentException: Cannot subclass final class pathname.FinalClass
at org.easymock.cglib.proxy.Enhancer.generateClass(Enhancer.java:565)
at org.easymock.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
at ...

所以我需要从CSVRecord分离“最终”修饰符。我用javassist尝试过这个。但是,我遇到了一个错误。看看这个简约的例子:

public class MyTestClass extends EasyMockSupport {

    @Mock
    private MockedClass mockedClass;

    @TestSubject
    private MyClass classUnderTest = new AmountConverter();

    @Test
    public void testName() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.get(FinalClass.class.getName());
        ctClass.defrost();
        removeFinal(ctClass);
        FinalClass finalClass = (FinalClass) EasyMock.createMock(ctClass.toClass());
        expect(mockedClass.foo()).andReturn(finalClass);

        replayAll();

        classUnderTest.foo();
    }

        static void removeFinal(CtClass clazz) throws Exception {
        int modifiers = clazz.getModifiers();
        if(Modifier.isFinal(modifiers)) {
            System.out.println("Removing Final");
            int notFinalModifier = Modifier.clear(modifiers, Modifier.FINAL);
            clazz.setModifiers(notFinalModifier);
        }
    }
}

public class MyClass {

    @Inject
    private MockedClass mockedClass;

    public void foo() {
        mockedClass.foo();
    }

    class MockedClass {

        FinalClass foo() {
            return null;
        }

    }
}

并在其自己的类文件中

public final class FinalClass {

}

我收到以下错误

javassist.CannotCompileException: by java.lang.LinkageError: loader (instance of  sun/misc/Launcher$AppClassLoader): attempted  duplicate class definition for name: "pathname/FinalClass"
at javassist.ClassPool.toClass(ClassPool.java:1099)
at javassist.ClassPool.toClass(ClassPool.java:1042)
at javassist.ClassPool.toClass(ClassPool.java:1000)
at javassist.CtClass.toClass(CtClass.java:1224)
...

2 个答案:

答案 0 :(得分:2)

您不能以这种方式更改已加载类的定义。

问题是构造FinalClass.class.getName()或更具体的类文本FinalClass.class已经加载了类来生成关联的Class对象,加载类的运行时表示

假设您之前没有以任何其他方式使用该类,您只需将代码更改为

ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.get("qualified.name.of.FinalClass");
ctClass.defrost();
removeFinal(ctClass);
FinalClass finalClass = (FinalClass) EasyMock.createMock(ctClass.toClass());

在创建运行时表示之前更改类的定义。

答案 1 :(得分:0)

我认为最好使用替代EasyMock而不是玩Javassist。

您可以使用支持模拟最终课程的Mockito 2:Mock the unmockable: opt-in mocking of final classes/methods

另一种选择是使用PowerMock:https://github.com/powermock/powermock/wiki/MockFinal