spring 测试中模拟的 aop 方面

aop aspects as mock in spring test

我看到了一篇有趣的文章:AOP Aspects as mocks in JUnit

由于我需要模拟多个最终和私有静态变量,我计划使用 AOP 代替反射或 PowerMockito,因为它们会导致 SpringJUnit4ClassRunner.

出现问题

有什么方法可以在不使用注释 @EnableAspectJAutoProxy 的情况下使用 @Aspect 进行测试 classes 吗? (我只想在一个测试用例中使用方面定位 class X。)

这是我想做的事的示例。

问题已回答(添加以讨论可以做什么)

//External class 
public final class ABC(){
  public void method1() throws Exception {}
}
@Service
public void DestClass() {
  private static final ABC abc = new ABC();

  public Object m() {
    // code (...)
    try {
      abc.method1();
    }
    catch(Exception e) {
      // do something (...)
      return null;
    }
    // more code (...)
  }
}

Spring 框架允许以编程方式创建通知目标对象的代理,无需通过 @EnableAspectJAutoProxy<aop:aspectj-autoproxy>

配置

详细信息可以在文档部分找到:Programmatic Creation of @AspectJ Proxies并且实现非常简单。

文档中的示例代码。

// create a factory that can generate a proxy for the given target object
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);

// add an aspect, the class must be an @AspectJ aspect
// you can call this as many times as you need with different aspects
factory.addAspect(SecurityManager.class);

// you can also add existing aspect instances, the type of the object supplied must be an @AspectJ aspect
factory.addAspect(usageTracker);

// now get the proxy object...
MyInterfaceType proxy = factory.getProxy();

请注意,使用 Spring AOP,只能建议方法执行。摘自documentation

Spring AOP currently supports only method execution join points (advising the execution of methods on Spring beans). Field interception is not implemented, although support for field interception could be added without breaking the core Spring AOP APIs. If you need to advise field access and update join points, consider a language such as AspectJ.

与问题共享的文档是关于 aspectj 的,并且没有提供要建议的示例代码,很难断定是否可以通过 Spring AOP 实现要求。文档也提到了这一点。

One example of the integration of AspectJ is the Spring framework, which now can use the AspectJ pointcut language in its own AOP implementation. Spring’s implementation is not specifically targeted as a test solution.

希望这对您有所帮助。

--- 更新:不使用 AOP 的测试用例 ---

考虑外部Class

public class ABCImpl implements ABC{

    @Override
    public void method1(String example) {
        System.out.println("ABC method 1 called :"+example);
    }
}

还有 DestClass

@Service
public class DestClass {

    private static final ABC service = new ABCImpl();

    protected ABC abc() throws Exception{
        System.out.println("DestClass.abc() called");
        return service;
    }

    public Object m() {
        Object obj = new Object();
        try {
            abc().method1("test");
        } catch (Exception e) {
            System.out.println("Exception : "+ e.getMessage());
            return null;
        }
        return obj;

    }
}

下面的测试 class 自动装配 DestClass bean 并覆盖逻辑以抛出异常。可以修改此代码以适应您的要求。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { DestClassSpringTest.TestConfiguration.class })
public class DestClassSpringTest {

    @Configuration
    static class TestConfiguration {

        @Bean
        public DestClass destClass() {
            return new DestClass() {
                protected ABC abc() throws Exception {
                //  super.abc(); // not required . added to demo the parent method call
                    throw new Exception("Custom exception thrown");
                }
            };

        }
    }

    @Autowired
    DestClass cut;

    @Test
    public void test() {
        Object obj = cut.m();
        assertNull(obj);
    }
}

以下为输出日志

DestClass.abc() called // this will not happen if the parent method call is commented in  DestClassSpringTest.TestConfiguration
Exception : Custom exception thrown

您所指的文章使用的是完整的 AspectJ,而不是 Spring AOP。因此,你不需要任何 @EnableAspectJAutoProxy,只需要

  • 当 运行 你的测试通过 -javaagent:/path/to/aspectjweaver.jar

    时,命令行上的 AspectJ 加载时编织器
  • AspectJ 编译器在编译您的测试时激活(如果您使用 Maven,可以通过 AspectJ Maven 插件轻松完成)

这两种方法都完全独立于 Spring,适用于任何项目,甚至在使用 Spring 时也适用于以执行第三方代码为目标,因为不像 [=] 那样需要动态代理39=] AOP。因此,无需将目标代码制作成 Spring bean 或在您的应用程序中为它创建包装器方法 class。使用编译时织入时,您甚至可以通过使用 call() 而不是 execution() 切入点来避免织入第三方库。 SpringAOP只知道execution(),AspectJ更强大

顺便说一句:很遗憾,您的问题和您对找到的解决方案的评论都有些模糊,我不完全理解您的要求。例如。您谈到了模拟 final 和私有静态变量,这也可以通过使用 set() and/or get() 切入点以其他方式与 AspectJ 一起实现。但实际上你似乎不需要模拟字段内容,只需将方法调用的结果存根到分配给这些字段的对象上。