Spock -Unit Test:如何为使用Mono的@around注释编写Spock单元测试 [英] Spock -Unit Test:How to write spock unit test for @around annotation which takes Mono

查看:50
本文介绍了Spock -Unit Test:如何为使用Mono的@around注释编写Spock单元测试的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用以下代码在我的webflux应用程序中使用aop打印日志,我在编写单元/集成测试时遇到麻烦?我们可以在此处验证日志交互吗?可以提供任何帮助

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

@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中返回

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.

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批注编写集成测试,因为它只是在记录不确定不确定如何写断言日志语句的集成测试

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

推荐答案

就像我在评论中说的那样,如果不使用PowerMock之类的附加工具,就无法轻松模拟 private static final 字段.我认为无论何时需要这样的东西,都应该重构代码以提高可测试性.这是一个远非完美的想法,但是我想给您一个关于如何对方面进行单元测试的想法.至于集成测试,您也可以这样做,但问自己要测试的内容:方面或Spring AOP切入点匹配是否正确?

Like I said in my comment, you cannot easily mock a private static final field without using add-on tools like PowerMock or similar. 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?

无论如何,让我们假设您正在测试的类是:

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

看看我如何将lambda分解为辅助方法?它有两个作用:

See how I factored out the lambda into a helper method? It has two effects:

  • 这使 logAround(ProceedingJoinPoint)建议方法更具可读性.
  • 它允许您对helper方法进行存根,而不是验证日志记录已完成,您只需验证是否为 Mono 结果调用了helper方法(而不是对其他结果类型调用了).
  • li>
  • It makes the logAround(ProceedingJoinPoint) advice method more readable.
  • 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).

最简单形式的测试如下所示:

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

请注意我如何使用 Spy (即基于原始对象的部分模拟)来有选择地存根helper方法.

Please note how I use a Spy (i.e. a partial mock based on the original object) in order to selectively stub the helper method.

更新:进行更全面的测试的另一种方法是将日志记录框架配置为登录到您可以控制和验证的目标,例如登录到内存数据库或您可以访问的缓冲区.

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, e.g. log into an in-memory database or into a buffer which you can access.

这篇关于Spock -Unit Test:如何为使用Mono的@around注释编写Spock单元测试的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆