从第 3 方移除最终修饰符 class
Remove final modifier from 3rd party class
我需要测试编写一个 JUnit 测试来测试以下行:
CSVRecord csvRecord = csvReader.readCsv(filename);
org.apache.commons.csv 中的 CSVRecord
是最终的 class。如果我尝试使用 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 class MyClass {
@Inject
private MockedClass mockedClass;
public void foo() {
mockedClass.foo();
}
class MockedClass {
FinalClass foo() {
return null;
}
}
}
并且在它自己的 class 文件中
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。
您可以使用支持 mocking final 的 Mockito 2 类:Mock the unmockable: opt-in mocking of final classes/methods
另一种选择是使用 PowerMock:https://github.com/powermock/powermock/wiki/MockFinal
您不能通过这种方式更改已加载 class 的定义。
问题是构造 FinalClass.class.getName()
或更具体的 class 文字 FinalClass.class
确实已经加载 class 以生成关联的 Class
对象,加载的 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());
在创建运行时表示之前更改 class 的定义。
我需要测试编写一个 JUnit 测试来测试以下行:
CSVRecord csvRecord = csvReader.readCsv(filename);
org.apache.commons.csv 中的 CSVRecord
是最终的 class。如果我尝试使用 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 class MyClass {
@Inject
private MockedClass mockedClass;
public void foo() {
mockedClass.foo();
}
class MockedClass {
FinalClass foo() {
return null;
}
}
}
并且在它自己的 class 文件中
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。
您可以使用支持 mocking final 的 Mockito 2 类:Mock the unmockable: opt-in mocking of final classes/methods
另一种选择是使用 PowerMock:https://github.com/powermock/powermock/wiki/MockFinal
您不能通过这种方式更改已加载 class 的定义。
问题是构造 FinalClass.class.getName()
或更具体的 class 文字 FinalClass.class
确实已经加载 class 以生成关联的 Class
对象,加载的 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());
在创建运行时表示之前更改 class 的定义。