在春季测试中模拟AOP方面

时间:2020-05-09 20:37:06

标签: spring-mvc junit4 spring-aop spring-test

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

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

有什么方法可以将@Aspect用于测试类而无需使用注释@EnableAspectJAutoProxy? (我只想在一个测试用例中使用面向类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 (...)
  }
}

2 个答案:

答案 0 :(得分:0)

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当前仅支持方法执行连接点 (建议在Spring bean上执行方法)。领域 尽管支持字段,但拦截尚未实现 可以在不破坏核心Spring AOP API的情况下添加拦截功能。 如果您需要建议现场访问和更新连接点,请考虑 语言,例如AspectJ。

与该问题共享的文档是关于Aspectj的,并且在没有提供示例代码的情况下,很难推断出是否可以通过Spring AOP实现需求。该文档也提到了这一点。

与AspectJ集成的一个示例是Spring框架, 现在可以在其自己的AOP中使用AspectJ切入点语言 实施。 Spring的实施并非专门针对 作为测试解决方案

希望这会有所帮助。

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

考虑外部类

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;

    }
}

下面的测试类将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

答案 1 :(得分:0)

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

    通过-javaagent:/path/to/aspectjweaver.jar

    运行测试时,在命令行上
  • 之一

  • 编译测试时激活了AspectJ编译器(如果使用Maven,则可以通过AspectJ Maven插件轻松完成)

这两种方法都完全独立于Spring,在任何项目中都可以使用,即使在使用Spring时也可以在针对第三方代码的执行中使用,因为与Spring AOP不同,不需要动态代理。因此,无需将目标代码放入Spring bean或在应用程序类中为其创建包装方法。使用编译时编织时,您甚至可以避免使用call()而不是execution()切入点编织到第三方库中。 Spring AOP只知道execution(),AspectJ更强大。

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