带有Junit的JavassistAPI

带有Junit的JavassistAPI,java,junit,javassist,Java,Junit,Javassist,我试图在每次测试之前更改一些第三方类定义,以模拟不同的结果。我必须使用类似javassist的东西,因为由于访问修饰符的原因,有时候扩展类是不可能的。下面是一个示例,说明了我试图将javassist和junit结合起来做什么: public class SimulatedSession extends SomeThirdParty { private boolean isJoe = false; public SimulatedSession(final boolean isJ

我试图在每次测试之前更改一些第三方类定义,以模拟不同的结果。我必须使用类似javassist的东西,因为由于访问修饰符的原因,有时候扩展类是不可能的。下面是一个示例,说明了我试图将javassist和junit结合起来做什么:

public class SimulatedSession extends SomeThirdParty {

    private boolean isJoe = false;
    public SimulatedSession(final boolean isJoe) {
        this.isJoe = isJoe;
    }

    @Override
    public void performThis() {
        final ClassPool classPool = ClassPool.getDefault();
        final CtClass internalClass = classPool.get("some.package.Class");
        final CtMethod callMethod = internalClass.getDeclaredMethod("doThis");
        if (isJoe) {
            callMethod.setBody("{System.out.println(\"Joe\");}");
        } else {
            callMethod.setBody("{System.out.println(\"mik\");}");
        }
        internalClass.toClass();
    }
}

@Test
public void firstTest() {
     SimulatedSession toUse = new SimulatedSession(false);
     // do something with this object and this flow
}

@Test
public void nextTest() {
     SimulatedSession toUse = new SimulatedSession(true);
     // do something with this object and this flow
}

如果我单独运行每个测试,我可以很好地运行代码。当我使用单元套件运行它们时,一个接一个的测试,我得到一个“冻结类问题”。为了解决这个问题,我正在研究这个问题,但是,我必须承认,我不确定如何使用不同的类池来解决这个问题。

可能是另一种方法。我们有一个类似的问题,因为我们曾经嘲笑一个依赖-我们不能重置它。因此,我们做了以下工作:在每次测试之前,我们用mock替换“live”实例。测试之后,我们恢复活动实例。因此,我建议您为每个测试替换修改后的第三方代码实例

@Before
public void setup()
{
    this.liveBeanImpl = (LiveBean) ReflectionTools.getFieldValue(this.beanToTest, "liveBean");
    ReflectionTools.setFieldValue(this.beanToTest, "liveBean", new TestStub());
}


@After
public void cleanup()
{
    ReflectionTools.setFieldValue(this.beanToTest, "liveBean", his.liveBeanImpl);
}
setFieldValue如下所示:

public static void setFieldValue(Object instanceToModify, String fieldName, Object valueToSet)
{
    try
    {
        Field declaredFieldToSet = instanceToModify.getClass().getDeclaredField(fieldName);
        declaredFieldToSet.setAccessible(true);
        declaredFieldToSet.set(instanceToModify, valueToSet);
        declaredFieldToSet.setAccessible(false);
    }
    catch (Exception exception)
    {
        String className = exception.getClass().getCanonicalName();
        String message = exception.getMessage();
        String errorFormat = "\n\t'%s' caught when setting value of field '%s': %s";
        String error = String.format(errorFormat, className, fieldName, message);
        Assert.fail(error);
    }
}

因此,如果您为每个测试重置实现,您的测试可能会通过。你明白了吗?

你当前的代码会尝试将同一个类加载两次到同一个
类加载器中,这是禁止的,你只能为给定的
类加载器加载一次类

为了让您的单元测试通过,我必须:

  • 创建我自己的临时
    ClassLoader
    ,它将能够加载
    一些.package.Class
    (出于测试目的,我将其替换为
    javassist.MyClass
    ),并且它将以这样的方式实现,即它将首先尝试在父级CL之前从中加载类
  • 将自己的
    ClassLoader
    设置为上下文
    ClassLoader
  • 更改
    SimulatedSession#performThis()
    的代码,以便能够获取由此方法创建的类实例,并调用
    internalClass.defrost()
    以防止“冻结类问题”
  • 通过反射调用方法
    doThis()
    ,通过使用
    SimulatedSession返回的类实例#performThis()
    ,确保使用的类已加载了我的
    类加载器
  • 因此,假设我的类
    javassist.MyClass
    是:

    package javassist;
    
    public class MyClass {
        public void doThis() {
    
        }
    }
    
    方法
    SimulatedSession#执行此()
    ,并进行了以下修改:

    public Class<?> performThis() throws Exception {
        final ClassPool classPool = ClassPool.getDefault();
        final CtClass internalClass = classPool.get("javassist.MyClass");
        // Prevent the "frozen class issue"
        internalClass.defrost();
        ...
        return internalClass.toClass();
    }
    

    看一看。这是一个基于Javassist的库,我为运行类似这样的测试而编写。它比使用原始Javassist更简洁。

    您的测试甚至不编译,我猜您的意思是
    新建模拟会话(false)@NicolasFilotto我的意思更像是一个例子,但我更新了这个问题,这样人们就可以看到我在尝试做什么。调用perfumrth将调用此方法,您是正确的。很好!这确实是一个令人印象深刻的解决方案。
    
    // The custom CL
    private URLClassLoader cl;
    // The previous context CL
    private ClassLoader old;
    
    @Before
    public void init() throws Exception {
        // Provide the URL corresponding to the folder that contains the class
        // `javassist.MyClass`
        this.cl = new URLClassLoader(new URL[]{new File("target/classes").toURI().toURL()}){
            protected Class<?> loadClass(String name, boolean resolve)
                throws ClassNotFoundException {
                try {
                    // Try to find the class for this CL
                    return findClass(name);
                } catch( ClassNotFoundException e ) {
                    // Could not find the class so load it from the parent
                    return super.loadClass(name, resolve);
                }
            }
        };
        // Get the current context CL and store it into old
        this.old = Thread.currentThread().getContextClassLoader();
        // Set the custom CL as new context CL
        Thread.currentThread().setContextClassLoader(cl);
    }
    
    @After
    public void restore() throws Exception {
        // Restore the context CL
        Thread.currentThread().setContextClassLoader(old);
        // Close the custom CL
        cl.close();
    }
    
    
    @Test
    public void firstTest() throws Exception {
        SimulatedSession toUse = new SimulatedSession(false);
        Class<?> c = toUse.performThis();
        // Invoke doThis() by reflection
        Object o2 = c.newInstance();
        c.getMethod("doThis").invoke(o2);
    }
    
    @Test
    public void nextTest() throws Exception {
        SimulatedSession toUse = new SimulatedSession(true);
        Class<?> c = toUse.performThis();
        // Invoke doThis() by reflection
        Object o2 = c.newInstance();
        c.getMethod("doThis").invoke(o2);
    }
    
    mik
    Joe