java - Spock -Unit Test:如何为采用 Mono 的 @around 注释编写 spock 单元测试
问题描述
嗨,我正在使用以下代码在我的 webflux 应用程序中使用 aop 打印日志,我在编写单元/集成测试时遇到问题?我们可以在这里验证日志交互吗?任何帮助将不胜感激
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Loggable {}
@Aspect
@Slf4j
public class LoggerAspect {
@Around("@annotation(Loggable)")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
var result = joinPoint.proceed();
if (result instanceof Mono) {
var monoResult = (Mono) result;
AtomicReference<String> traceId = new AtomicReference<>("");
return monoResult
.doOnSuccess(o -> {
var response = "";
if (Objects.nonNull(o)) {
response = o.toString();
}
log.info("Enter: {}.{}() with argument[s] = {}",
joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(),
joinPoint.getArgs());
log.info("Exit: {}.{}() had arguments = {}, with result = {}, Execution time = {} ms",
joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(),
joinPoint.getArgs()[0],
response, (System.currentTimeMillis() - start));
});
}
}
}
测试失败。不知何故,当我调试指针没有进入 doOnNext 方法时。而且我不确定如何在上面的 Logging 方面断言日志交互。在 Junit5 中,我知道我可以为每个方法使用 mockito 并返回一些东西,但是我如何在 spock 中重新返回.
class LogAspectTest extends Specification {
private static final String MOCK_METHOD_LOG_VALUE = "mockMethodLogValue"
private Logger log = Mock()
private ProceedingJoinPoint mockJoinPoint = Mock()
private static Mono<String> methodReturn = Mono.just(["Data", "Data"])
private LogAspect logAspect = new LogAspect(log)
@Unroll
def 'logAround verify log interaction'() {
given:
mockJoinPoint.proceed() == Mono.just("Hello")
final Method method = TestClass.class.getMethod("mockMethod")
when:
logAspect.logAround(mockJoinPoint)
then:
interaction { mockJoinPointAndMethodSignatureInteractions(method, methodReturnToUse) }
where:
resultType | methodReturnToUse
'Mono' | methodReturn
}
private void mockJoinPointAndMethodSignatureInteractions(Method method, Publisher result) {
1 * mockJoinPoint.proceed() >> result
1 * log.info() >> ""
}
private static class TestClass {
@Loggable
Mono<String> mockMethod() { return Mono.just("data") }
}
}
是否建议为 @Loggable 注释编写集成测试,因为它只是记录不确定如何编写断言日志语句的集成测试
解决方案
就像我在评论中所说的那样,如果private static final
不使用 PowerMock 或类似的附加工具,您将无法轻松地模拟一个字段。我认为每当你需要这样的东西时,你应该重构你的代码以获得更好的可测试性。这是一个远非完美的想法,但我想给你一个关于如何对你的方面进行单元测试的想法。至于集成测试,你也可以这样做,但问问自己你想测试什么:真正的方面或 Spring AOP 切入点匹配是否正常工作?
无论如何,让我们假设您正在测试的课程是:
package de.scrum_master.stackoverflow.q64164101;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Loggable {}
package de.scrum_master.stackoverflow.q64164101;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
import java.util.Objects;
import java.util.function.Consumer;
@Aspect
public class LogAspect {
private static final Logger log = LoggerFactory.getLogger(LogAspect.class.getName());
@Around("@annotation(Loggable)")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
if (result instanceof Mono)
return ((Mono) result).doOnSuccess(getConsumer(joinPoint, start));
return result;
}
public Consumer getConsumer(ProceedingJoinPoint joinPoint, long start) {
return o -> {
String response = "";
if (Objects.nonNull(o))
response = o.toString();
log.info("Enter: {}.{}() with argument[s] = {}",
joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(),
joinPoint.getArgs());
log.info("Exit: {}.{}() had arguments = {}, with result = {}, Execution time = {} ms",
joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(),
joinPoint.getArgs()[0],
response, (System.currentTimeMillis() - start));
};
}
}
看看我是如何将 lambda 分解为辅助方法的?它有两个作用:
- 它使
logAround(ProceedingJoinPoint)
建议方法更具可读性。 - 它允许您存根辅助方法,而不是验证日志记录是否完成,您只需验证辅助方法是否已为
Mono
结果调用(而不是为其他结果类型调用)。
最简单形式的测试可能如下所示:
package de.scrum_master.stackoverflow.q64164101
import org.aspectj.lang.ProceedingJoinPoint
import reactor.core.publisher.Mono
import spock.lang.Specification
class LogAspectTest extends Specification {
LogAspect logAspect = Spy()
ProceedingJoinPoint joinPoint = Mock()
def "aspect target method returns a Mono"() {
given:
joinPoint.proceed() >> Mono.just("Hello")
when:
logAspect.logAround(joinPoint)
then:
1 * logAspect.getConsumer(joinPoint, _)
}
def "aspect target method does not return a Mono"() {
given:
joinPoint.proceed() >> "dummy"
when:
logAspect.logAround(joinPoint)
then:
0 * logAspect.getConsumer(joinPoint, _)
}
}
请注意我如何使用Spy
(即基于原始对象的部分模拟)来选择性地存根辅助方法。
更新:更集成测试的另一种方法是配置您的日志框架以登录到您可以控制和验证的目标,例如登录到内存数据库或您可以访问的缓冲区。
推荐阅读
- c# - How to generate an auto-incrementing ID number that resets yearly
- javascript - Vue中如何将格式化数据存储在数组中,watch和v-model不保存格式化数据?
- flutter - FLUTTER FIRESTORE:更新布尔字段:“String”类型不是“DocumentReference”类型的子类型
- jupyter-notebook - how to open a jupyter notebook on windows 8.1?
- javascript - CRC16-CCITT打包(将C代码转换为(JS duktape))
- android - 在 NavigationDrawer 中添加 ViewPager
- html - 布局中的文章中的 Flexbox 和 UL/LI - 如何确保其正确适配?
- apache-spark - AWS EMR YARN 集群上的 Jupyterhub pyspark3
- flutter - Flutter 函数的默认值
- sql-server - [SSISDB].[internal].[executable_statistics] 为空