Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/323.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/spring-mvc/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 如何在SpringMVC应用程序中测试方面_Java_Spring Mvc_Mockito_Aspectj_Spring Test - Fatal编程技术网

Java 如何在SpringMVC应用程序中测试方面

Java 如何在SpringMVC应用程序中测试方面,java,spring-mvc,mockito,aspectj,spring-test,Java,Spring Mvc,Mockito,Aspectj,Spring Test,我有一个SpringMVC应用程序,其中我使用一个方面来捕获所有控制器方法中的异常 @Component @Aspect public class ControllerExceptionAspect { private Logger logger; public ControllerExceptionAspect() { logger = Logger.getLogger(ControllerExceptionAspect.class); }

我有一个SpringMVC应用程序,其中我使用一个方面来捕获所有控制器方法中的异常

@Component
@Aspect
public class ControllerExceptionAspect {

    private Logger logger;

    public ControllerExceptionAspect() {
       logger = Logger.getLogger(ControllerExceptionAspect.class);
    }

    public ControllerExceptionAspect(Logger logger) {
       this.logger = logger;
    }

    // Catching all exceptions from all methods in all controllers classes

    @AfterThrowing(pointcut = "execution(* com.my.package..controller..*(..))", throwing = "exception")
    public void afterThrowingAdvice(Exception exception) {
       logger.error("CONTROLLER ASPECT: EXCEPTION IN METHOD -> " +    
       exception.getClass());
    }
}
Aspect运行良好,但不幸的是我无法测试它。我尝试了很多次,但在我模拟控制器中的异常后,无法获取如何捕获是否调用了方面方法

@SuppressWarnings("ALL")
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextHierarchy({
        @ContextConfiguration(classes = RootConfig.class),
        @ContextConfiguration(classes = WebConfig.class)
})
public class ControllerExceptionAspectTest {

    @Autowired
    ApplicationContext applicationContext;

    @Test
    public void testControllerExceptionAspectGetsExecutedWhenExceptionOccures(){
        HomeController homeController = (HomeController)applicationContext.getAutowireCapableBeanFactory().getBean("homeController");
        try{homeController.callMethod("00000");}
        catch (Exception e){}
        ControllerExceptionAspect controllerExceptionAspect = (ControllerExceptionAspect)applicationContext.getAutowireCapableBeanFactory().getBean("controllerExceptionAspect");
        // HOW TO CATCH THAT ASPECT METHOD WAS CALLED???
    }
}

我认为您试图实现的是测试您创建的配置(方面切入点),而不是可以进行单元测试的方面本身。我担心的是,没有简单的方法可以做到这一点

你可以根据互联网上的一些建议来获取日志或其他想法。老实说,只有在您确实需要测试方面是否被调用时,我才会测试方面的预期行为。如果它正在登录,我不会这么做。如果它设置了一些db(或其他副作用),我会验证该值是否在db中。这就是集成测试的草率基础

如果您真的必须以您想要的方式测试方面,那么您可以编写与给定代码类似的东西。但请记住,正常(非测试)运行时spring配置需要spring上下文中存在的验证器接口的虚拟实现

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Config.class)
public class AspectTesting {

    @Autowired
    ServiceWithAspect service;

    @Autowired
    Verifier verifyingAspect;

    @Test
    public void test() {
        // given
        boolean condition = false;

        // when
        try {
            service.doit();
        } catch (Exception swallow) {}

        // then
        try {
            condition = ((VerifyingAspect) ((Advised) verifyingAspect).getTargetSource().getTarget()).wasExecuted();
        } catch (Exception swallow) {}

        // then
        Assert.assertTrue(condition);
    }
}

@Configuration
@EnableAspectJAutoProxy
@ComponentScan("aspects")
class Config {
}

@Component
class VerifyingAspect implements Verifier {

    private boolean executed = false;

    public boolean wasExecuted() {
        return executed;
    }

    @Override
    public void invoked() {
        executed = true;
    }
}

@Service
class ServiceWithAspect {
    public void doit() {
        throw new RuntimeException();
    }
}

@Component
@Aspect
class TestedAspect {

    @Autowired
    Verifier verifier;

    @AfterThrowing(pointcut = "execution(* *(..))", throwing = "exception")
    public void afterThrowingAdvice(Exception exception) {
        // your aspect logic here
        verifier.invoked();
    }
}

interface Verifier {
    void invoked();
}

孤立地测试一个方面(包括它的切入点表达式)非常容易,不需要整个web上下文(或任何上下文)

我将首先尝试给出一个广义的例子,而不是OP问题中的例子

假设我们有一个方面,如果方法的第一个参数为null,则必须抛出异常,否则允许方法调用继续

它应仅应用于使用自定义
@ThrowOnNullFirstArg
注释进行注释的控制器

@Aspect
public class ThrowOnNullFirstArgAspect {
    @Pointcut("" +
            "within(@org.springframework.stereotype.Controller *) || " +
            "within(@(@org.springframework.stereotype.Controller *) *)")
    private void isController() {}

    @Around("isController()")
    public Object executeAroundController(ProceedingJoinPoint point) throws Throwable {
        throwIfNullFirstArgIsPassed(point);
        return point.proceed();
    }

    private void throwIfNullFirstArgIsPassed(ProceedingJoinPoint point) {
        if (!(point.getSignature() instanceof MethodSignature)) {
            return;
        }

        if (point.getArgs().length > 0 && point.getArgs()[0] == null) {
            throw new IllegalStateException("The first argument is not allowed to be null");
        }
    }
}
我们可以这样测试:

public class ThrowOnNullFirstArgAspectTest {
    private final ThrowOnNullFirstArgAspect aspect = new ThrowOnNullFirstArgAspect();
    private TestController controllerProxy;

    @Before
    public void setUp() {
        AspectJProxyFactory aspectJProxyFactory = new AspectJProxyFactory(new TestController());
        aspectJProxyFactory.addAspect(aspect);

        DefaultAopProxyFactory proxyFactory = new DefaultAopProxyFactory();
        AopProxy aopProxy = proxyFactory.createAopProxy(aspectJProxyFactory);

        controllerProxy = (TestController) aopProxy.getProxy();
    }

    @Test
    public void whenInvokingWithNullFirstArg_thenExceptionShouldBeThrown() {
        try {
            controllerProxy.someMethod(null);
            fail("An exception should be thrown");
        } catch (IllegalStateException e) {
            assertThat(e.getMessage(), is("The first argument is not allowed to be null"));
        }
    }

    @Test
    public void whenInvokingWithNonNullFirstArg_thenNothingShouldBeThrown() {
        String result = controllerProxy.someMethod(Descriptor.builder().externalId("id").build());
        assertThat(result, is("ok"));
    }

    @Controller
    @ThrowOnNullFirstArg
    private static class TestController {
        @SuppressWarnings("unused")
        String someMethod(Descriptor descriptor) {
            return "ok";
        }
    }
}
关键部分在
setUp()
方法中。请注意,它还允许验证切入点表达式的正确性

如何测试aspect方法是否实际被调用? 如果aspect方法只有一些难以在测试中验证的效果,那么可以使用Mockito之类的模拟库,在真实aspect周围创建存根,然后验证该方法是否实际被调用

private ControllerExceptionAspect aspect = Mockito.stub(new     ControllerExceptionAspect());
然后在测试中,通过代理调用控制器后

Mockito.verify(aspect).afterThrowingAdvice(Matchers.any());
如何测试aspect方法实际写入日志? 如果您使用的是logback classic,则可以编写一个
Appender
实现,并将其添加到感兴趣的
Logger
,然后检查预期的消息是否被记录

public class TestAppender extends AppenderBase<ILoggingEvent> {
    public List<ILoggingEvent> events = new ArrayList<>();

    @Override
    protected void append(ILoggingEvent event) {
        events.add(event);
    }
}
在你的测试中:

List<ILoggingEvent> errors = appender.events.stream()
        .filter(event -> event.getLevel() == Level.ERROR)
        .collect(Collectors.toList());
assertEquals("Exactly one ERROR is expected in log", 1, errors.size());
// any other assertions you need
List errors=appender.events.stream()
.filter(事件->事件.getLevel()==级别.ERROR)
.collect(Collectors.toList());
assertEquals(“日志中只应出现一个错误”,1,errors.size());
//您需要的任何其他断言

可能您还需要在
@After
方法中使用
Appender
,但我不确定。

如前所述,如果您要使用成熟的AspectJ而不是Spring AOP,您可以在测试中使用类似
adviceexecution()
的切入点。IMHO,日志的最后一种方法非常优雅
List<ILoggingEvent> errors = appender.events.stream()
        .filter(event -> event.getLevel() == Level.ERROR)
        .collect(Collectors.toList());
assertEquals("Exactly one ERROR is expected in log", 1, errors.size());
// any other assertions you need