Search code examples
javaaopjunit5aspect

How to test Aspects


I have found and tried to follow this answer by Roman Puchkovskiy with a detailed example, but I am missing some detail.

Here is the aspect I am trying to test:

package com.company.reporting.logger.consumer.prometheusmetrics;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.kafka.KafkaException;
import org.springframework.stereotype.Component;

@Aspect
@Component
@Slf4j
/**
 * AOP Class to capture error count due to kafka exception
 */
public class MetricsCollection {
    MeterRegistry meterRegistry;

    private final Counter counter;

    public MetricsCollection(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        counter = Counter.builder("Kafka.producer.error.count").description("Custom Kafka Producer metrics for business exception").tags("behavior", "exception").register(meterRegistry);
    }

    /***
     * point cut for jointPoint within service class execution
     */
    @Pointcut("within (@org.springframework.stereotype.Service *)")
    public void serviceBean() {
        // this is pointcut
    }

    /***
     * point cut for all the jointPoints
     */
    @Pointcut("execution(* *(..))")
    public void methodPointcut() {
        // this is pointcut
    }

    /***
     *
     * increasing counter upon kafka exception
     */
    @AfterThrowing(pointcut = "serviceBean() && methodPointcut()", throwing = "e")
    public void handleException(Exception e) {
        if (e instanceof KafkaException || e instanceof org.apache.kafka.common.KafkaException) {
            LOGGER.error("*** In Aspect ErrorHandler *** " + e.getLocalizedMessage());
            counter.increment();
        }

    }
}

And here is my unit test class:

package com.company.reporting.logger.consumer.prometheusmetrics;

import com.company.reporting.logger.consumer.utils.TestUtils;

import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
import org.apache.kafka.common.KafkaException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockitoAnnotations;
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;
import org.springframework.aop.framework.AopProxy;
import org.springframework.aop.framework.DefaultAopProxyFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;

class MetricsCollectionTest {

    private MetricsCollection aspect;

    private TestController controllerProxy;
    MeterRegistry meterRegistry;

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);

        meterRegistry = new CompositeMeterRegistry();
        aspect = new MetricsCollection(meterRegistry);
        AspectJProxyFactory aspectJProxyFactory = new AspectJProxyFactory(new TestController());
        aspectJProxyFactory.addAspect(aspect);

        DefaultAopProxyFactory proxyFactory = new DefaultAopProxyFactory();
        AopProxy aopProxy = proxyFactory.createAopProxy(aspectJProxyFactory);

        controllerProxy = (TestController) aopProxy.getProxy();
    }

    @Test
    void MetricsCollection() {
        MetricsCollection metricsCollection = new MetricsCollection(meterRegistry);

        assertNotNull(metricsCollection);
    }

    @Test
    void handleException() {
        ListAppender<ILoggingEvent> listAppender = TestUtils.getiLoggingEventListAppender(MetricsCollection.class);

        try {
            controllerProxy.throwKafkaException();

         } catch (Exception ex) {
            if (! (ex instanceof KafkaException)) {
                fail();
            }
        } finally {
            List<ILoggingEvent> logList = listAppender.list;

            assertEquals(1, logList.size());

        }
    }


    @Controller
    static
    class TestController {
        @Bean
        String throwKafkaException() {
            throw new KafkaException();
        }
    }

}

Finally, here is my TestUtils class:

package com.company.reporting.logger.consumer.utils;

import static org.junit.jupiter.api.Assertions.assertNotNull;

import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
import org.slf4j.LoggerFactory;

public class TestUtils {
       
    @NotNull
    public static ListAppender<ILoggingEvent> getiLoggingEventListAppender(Class clazz) {
        Logger logger = (Logger) LoggerFactory.getLogger(clazz);
        ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
        listAppender.setName(clazz.getName());
        listAppender.start();
        logger.addAppender(listAppender);
        return listAppender;
    }
}

My constructor test passes... :)

But the handleException() test is failing to trigger my aspect. Which dot on an i or cross bar on a t did I miss?


Solution

  • Your "unit test" is closer to an integration test, and you are testing the AOP framework more than the aspect itself. The only thing you do seem to test is the side effect of logging, which is a topic unrelated to AOP. For other ways to unit-test or integration-test aspects, see my linked answers.

    Apart from that and without having tried to copy and compile your code yet, what immediately strikes me as odd is that your aspect has a within (@org.springframework.stereotype.Service *) pointcut, but your target class seems to be a @Controller. I would not expect the aspect to match there. What happens if you fix that?