简体   繁体   English

Spock -Unit Test:如何为需要 Mono 的 @around 注释编写 spock 单元测试

[英]Spock -Unit Test:How to write spock unit test for @around annotation which takes Mono

Hi I am using following code to print logs using aop in my webflux app,I have trouble writing unit/integration tests ?can we verify log interactions here?Any help would be appreciated嗨,我正在使用以下代码在我的 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));
        });
    }
  }
}

Test Failing .测试失败 Somehow when i debug pointer is not going inside doOnNext Method.And I am not sure how I can assert log interaction in above Logging aspect.In Junit5 I know I can use mockito for each method and return something ,but how i can rerturn in spock.不知何故,当我调试指针没有进入 doOnNext 方法时。我不确定如何在上面的日志记录方面断言日志交互。在 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") }

  }
}

Is it recommended to write Integration Test for @Loggable annotation since it just logging not sure how can write Integration Test which assert the log statements是否建议为@Loggable 注释编写集成测试,因为它只是记录不知道如何编写声明日志语句的集成测试

Like I said in my comment, you cannot easily mock a private static final field without using add-on tools like PowerMock or similar.就像我在评论中所说的那样,如果不使用 PowerMock 或类似工具之类的附加工具,您将无法轻松模拟private static final字段。 I think that whenever you need something like that, you should rather refactor your code for better testability.我认为每当您需要类似的东西时,您应该重构代码以获得更好的可测试性。 Here is an idea which is far from perfect, but I want to give you an idea about how you could unit-test your aspect.这是一个远非完美的想法,但我想给你一个关于如何对你的方面进行单元测试的想法。 As for an integration test, you can also do that, but ask yourself what you want to test: really the aspect or that Spring AOP pointcut matching works correctly?至于集成测试,您也可以这样做,但问问自己您想测试什么:真的是方面还是 Spring AOP 切入点匹配正确工作?

Anyway, let us assume that your classes under test are:无论如何,让我们假设您的测试类是:

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));
    };
  }
}

See how I factored out the lambda into a helper method?看看我如何将 lambda 分解为辅助方法? It has two effects:它有两个作用:

  • It makes the logAround(ProceedingJoinPoint) advice method more readable.它使logAround(ProceedingJoinPoint)建议方法更具可读性。
  • It permits you to stub the helper method and instead of verifying that logging is done you just verify that the helper method was called for Mono results (and not called for other result types).它允许您存根 helper 方法,而不是验证日志记录是否完​​成,您只需验证是否为Mono结果调用了 helper 方法(而不是为其他结果类型调用)。

The test in its simplest form could look like this:最简单形式的测试可能如下所示:

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, _)
  }
}

Please note how I use a Spy (ie a partial mock based on the original object) in order to selectively stub the helper method.请注意我如何使用Spy (即基于原始对象的部分模拟)来选择性地存根辅助方法。


Update: An alternative for more integrative testing would be to configure your logging framework to log into a target which you can control and verify, eg log into an in-memory database or into a buffer which you can access.更新:更多集成测试的替代方法是将日志框架配置为登录到您可以控制和验证的目标,例如登录到内存数据库或您可以访问的缓冲区。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM