Java 从第三方类中删除最终修改器

Java 从第三方类中删除最终修改器,java,junit,javassist,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.c

我需要测试并编写一个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
中分离“final”修饰符。我用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 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)
...

我认为最好使用EasyMock的替代方案,而不是使用Javassist

您可以使用支持模拟最终类的Mockito 2:


另一种选择是使用PowerMock:

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

问题是构造
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());

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

模拟
csvReader
并让它在调用
readCsv(anyObject())
时返回真正的
CSVRecord
可能更容易。请不要模拟您不拥有的类型,您迟早会遇到麻烦。而是在它周围写一个适配器。有关更多详细信息,请查看和@SeverityOne:你不能这么做。你会如何初始化CSVRecord?@TheJavaGuy IvanMilosavljević1。使用适配器,我们将问题转移到另一个类。然后我们必须在那里测试readCsv方法。2.在共享链接中,必须构建CSVRecord。但是CSVRecord没有可访问的构造函数…@Chris311适配器实现了一个您定义的接口,该接口可以在单元测试中轻松模拟。接口是根据应用程序的业务逻辑定义的,通常比第三方组件的接口简单得多。真正的适配器本身是CSVRecord上非常薄的一层,它的工作是将应用程序的请求转换为CSVRecord。Adapter因此成为处理CSVRecord的唯一类。这样,您就可以轻松地测试应用程序逻辑。这类适配器除了转换之外通常没有内部逻辑。看起来是合法的,但我们不允许在项目中使用PowerMock。Mockito也是如此,因为我们正在使用EasyMock,开发人员已经认识到将两者合并是不必要的。。。
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.get("qualified.name.of.FinalClass");
ctClass.defrost();
removeFinal(ctClass);
FinalClass finalClass = (FinalClass) EasyMock.createMock(ctClass.toClass());