本文介绍了Spring AOP+AspectJ:@AfterReturning建议在嘲笑时错误执行(在实际执行之前)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!
问题描述
在集成测试中,我@AfterReturning
的建议被错误执行,而在测试中我模拟它抛出TimeoutException,传递给方面的arg为空。
我的建议:
@AfterReturning("execution(* xxxxxx" +
"OrderKafkaProducerService.sendOrderPaidMessage(..)) && " +
"args(order)")
public void orderComplete(CheckoutOrder order) { // order is null when debugging
metricService.orderPaidKafkaSent();
log.trace("Counter inc: order paid kafka"); // this line of log is shown in console
metricService.orderCompleted();
log.trace("Order complete! {}", order.getId()); // this line is not, because NPE
}
我的测试:
// mocking
doThrow(new ServiceException(new TimeoutException("A timeout occurred"), FAILED_PRODUCING_ORDER_MESSAGE))
.when(orderKafkaProducerService).sendOrderPaidMessage(any()); // this is where advice is executed, which is wrong
...
// when
(API call with RestAssured, launch a real HTTP call to endpoint; service is called during this process)
// then
verify(orderKafkaProducerService).sendOrderPaidMessage(any(CheckoutOrder.class)); // it should be called
verify(metricService, never()).orderCompleted(); // but we are throwing, not returning, we should not see this advice executed
由于NPE(订单为空),此测试失败。
在调试中,我发现在我模仿的时候,我已经执行了通知,并且在这一点上,any()
还没有值,是空的,所以NPE。但我认为,这个建议不应该在嘲笑的同时执行。我如何在测试时避免这种情况??这对我来说太荒谬了。
推荐答案
目前,Spring测试支持没有显式处理注入的模拟或间谍(它是通过Mockito的代理子类)以后可能实际上是AOP目标的情况(即,被代理,因此通过CGLIB再次子类化)。
Spring、Spring Boot和Mockito有几个与此主题相关的错误标签。目前还没有人对此采取任何行动。我确实理解为什么Mockito的维护者不会在他们的代码库中包含特定于Spring的东西,但是我不理解为什么Spring的人不改进他们的测试工具。
实际上,在调试failing test和检查kafkaService
时,您会发现以下事实:
kafkaService.getClass()
为com.example.template.services.KafkaService$MockitoMock$92961867$$EnhancerBySpringCGLIB$$8fc4fe95
kafkaService.getClass().getSuperclass()
为com.example.template.services.KafkaService$MockitoMock$92961867
kafkaService.getClass().getSuperclass().getSuperclass()
为class com.example.template.services.KafkaService
换言之:
kafkaService
是CGLIB Spring AOP代理。- AOP代理包装Mockito间谍(可能是ByteBuddy代理)。
- Mockito间谍包装原始对象。
不管怎样,你能做什么?
- 您要么使用this tutorial 中所述的复杂方法
- 或者您选择廉价的解决方案,通过
AopTestUtils.getTargetObject(Object)
显式地解包AOP代理。您可以安全地调用此方法,因为如果传递的候选对象不是Spring代理(内部易于识别,因为它实现了Advised
接口,该接口也提供对目标对象的访问),它只是再次返回传递的对象。
在您的情况下,后一种解决方案如下所示:
@Test
void shouldCompleteHappyPath() {
// fetch spy bean by unwrapping the AOP proxy, if any
KafkaService kafkaServiceSpy = AopTestUtils.getTargetObject(kafkaService);
// given mocked
doNothing().when(kafkaServiceSpy).send(ArgumentMatchers.any());
// when (real request)
testClient.create().expectStatus().isOk();
// then
verify(kafkaServiceSpy).send(ArgumentMatchers.any());
verify(metricService).incKafka();
}
这将导致when(kafkaServiceSpy).send(ArgumentMatchers.any())
不再触发方面建议,因为kafkaServiceSpy
不再是AOP代理。但是,自动连接的BeankafkaService
仍然是,因此AOP会像预期的那样被触发,但在记录模拟交互时不再意外地触发。实际上,对于验证,您甚至可以使用kafkaService
代替间谍,只在记录您稍后要验证的交互时才打开间谍:
@Test
void shouldCompleteHappyPath() {
// given mocked
doNothing()
.when(
// fetch spy bean by unwrapping the AOP proxy, if any
AopTestUtils.<KafkaService>getTargetObject(kafkaService)
)
.send(ArgumentMatchers.any());
// when(real request)
testClient.create().expectStatus().isOk();
// then
verify(kafkaService).send(ArgumentMatchers.any());
verify(metricService).incKafka();
}
附言:如果没有您的MCVE,我永远不可能调试这个程序并找出到底是怎么回事。这再次证明,包括MCVE在内的提问是您可以为自己做的最好的事情,因为它可以帮助您获得问题的答案,否则这些问题可能无法得到回答。
更新:我在类似的闭合问题Spring Boot #6871下提到了这个问题后,其中一个维护人员自己创建了Spring Boot #22281,在这里专门解决了您的问题。您可能需要查看新问题,以确定是否/何时可以修复该问题。
这篇关于Spring AOP+AspectJ:@AfterReturning建议在嘲笑时错误执行(在实际执行之前)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!