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

后端 未结 1 1640
鱼传尺愫
鱼传尺愫 2021-01-21 22:41

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 appre

相关标签:
1条回答
  • 2021-01-21 22:53

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

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

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

    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.

    0 讨论(0)
提交回复
热议问题