I need to test write a JUnit test which tests the following line:
CSVRecord csvRecord = csvReader.readCsv(filename);
with CSVRecord
from org.apache.commons.csv being a final class. If I try to tests this using EasyMock I get the following error:
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 ...
So I need to detach the "final" modifier from the CSVRecord
. I tried this with javassist. However, I am running into an error. Have a look at this minimalistic example:
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);
}
}
}
with
public class MyClass {
@Inject
private MockedClass mockedClass;
public void foo() {
mockedClass.foo();
}
class MockedClass {
FinalClass foo() {
return null;
}
}
}
and in it's own class file
public final class FinalClass {
}
I get the following error
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)
...
You can not change the definition of an already loaded class this way.
The problem is that the construct FinalClass.class.getName()
or more specific, the class literal FinalClass.class
, does already load the class to produce the associated Class
object, the runtime representation of the loaded class.
Assuming that you are not using the class in any other way before that, you simply have to change the code to
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.get("qualified.name.of.FinalClass");
ctClass.defrost();
removeFinal(ctClass);
FinalClass finalClass = (FinalClass) EasyMock.createMock(ctClass.toClass());
to change the definition of the class before its runtime representation is created.