簡體   English   中英

Spock -Unit Test:如何為需要 Mono 的 @around 注釋編寫 spock 單元測試

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

嗨,我正在使用以下代碼在我的 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 方法時。我不確定如何在上面的日志記錄方面斷言日志交互。在 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 注釋編寫集成測試,因為它只是記錄不知道如何編寫聲明日志語句的集成測試

就像我在評論中所說的那樣,如果不使用 PowerMock 或類似工具之類的附加工具,您將無法輕松模擬private static final字段。 我認為每當您需要類似的東西時,您應該重構代碼以獲得更好的可測試性。 這是一個遠非完美的想法,但我想給你一個關於如何對你的方面進行單元測試的想法。 至於集成測試,您也可以這樣做,但問問自己您想測試什么:真的是方面還是 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)建議方法更具可讀性。
  • 它允許您存根 helper 方法,而不是驗證日志記錄是否完​​成,您只需驗證是否為Mono結果調用了 helper 方法(而不是為其他結果類型調用)。

最簡單形式的測試可能如下所示:

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 (即基於原始對象的部分模擬)來選擇性地存根輔助方法。


更新:更多集成測試的替代方法是將日志框架配置為登錄到您可以控制和驗證的目標,例如登錄到內存數據庫或您可以訪問的緩沖區。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM