DEV Community

Cover image for Gradle + AspectJ + JUnit5
Vishal Bhavsar
Vishal Bhavsar

Posted on

Gradle + AspectJ + JUnit5

Problem Statement

When running JUnit5 tests using Gradle in Java, I wanted to log the arguments, that a method is receiving. One way was to put logger just before the method is executed. However, there are 2 challenges:

  1. If the method is being used 100 times, we have to add the logger in all 100 places.
  2. If the method is in 3rd party library, there are limitations and you can't really know what that method is actually receiving internally.

In this tutorial, I will use the following example.
My test case is validating that the input string is not null or blank. Here, I am using asserj-core library. So, my test case looks like:

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

@Test
@DisplayName("Verify string is not null & blank.")
public void testStringNotNull() {
    var input = "Hello world!";
    Assertions.assertThat(input).isNotBlank();
}
Enter fullscreen mode Exit fullscreen mode

Solution

Aspect Oriented Programming(AOP)

We can use the concept of AOP to weave some code at compile time and execute it before/after/around the desired method.

Pre-requisites

  • I am using below softwares:
    • Gradle: 8.6
    • Java: 17.0.10
    • Groovy: 3.0.17
    • Kotlin: 1.9.20
  • We are using aspectj library to weave the code.
  • This tutorial is specifically for tests residing in /src/test/java. However, with minor changes, you can achieve the same results for the code residing in /src/main/java.

Step 1: Modify build.gradle

Add plugin declaration.

plugins {
    // Other plugin declarations
    id "io.freefair.aspectj.post-compile-weaving" version "8.6"
}
Enter fullscreen mode Exit fullscreen mode

Add aspectj dependencies

dependencies {
    implementation "org.aspectj:aspectjrt:1.9.22"
    implementation "org.aspectj:aspectjweaver:1.9.22"

    // Other dependencies
}
Enter fullscreen mode Exit fullscreen mode

Add following snippet

compileTestJava {
    ajc {
        options {
            aspectpath.setFrom configurations.aspect
            compilerArgs = ["-Xlint:ignore", "-Xajruntimetarget:1.5"]
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Add Aspect & advise

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;

@Slf4j
@Aspect
public class LoggingAspect {
    @SneakyThrows
    @Before("call(* org.assertj.core.api.Assertions.assertThat(..))")
    public void beforeAssert(final JoinPoint joinPoint) {
        log.debug("Execution before invocation of AbstractCharSequenceAssert.isNotBlank().");
        final var args = joinPoint.getArgs();
        var method = MethodSignature.class.cast(joinPoint.getSignature()).getMethod();
        final var parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            final Class<?> parameterType = parameters[i].getType();
            final var parameterValue = args[i];
            log.info("Param type: {}, value: {}", parameterType, parameterValue);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This class represents an Aspect, which cuts across multiple objects.

@Before("call(* org.assertj.core.api.Assertions.assertThat(..))")
public void beforeAssert(final JoinPoint joinPoint) {
    // code
}
Enter fullscreen mode Exit fullscreen mode

This line defines the before advice. The method should return void. The method should be declared public. It takes 1 parameter of type JoinPoint. The value for this annotation is the regular expression of advice declaration.

Here, in this example, we are specifying to execute code before the invocation of Assertions.assertThat(). Any custom logic that you want to perform goes inside the beforeAssert() method. Here, I am extracting the parameters & their values of a method being intercepted, in this case Assertions.assertThat(input). As you can see assertThat() method is taking only 1 parameter of type java.lang.String.

Once you run the code, you should see the following output in the console.

Execution before invocation of AbstractCharSequenceAssert.isNotBlank().
Param type: class java.lang.String, value: Hello world!
Enter fullscreen mode Exit fullscreen mode

Few things to remember

  • Your aspect needs to be in the same sources root as your tests. i.e. src/test/java

Top comments (1)

Collapse
 
thmsondavid20 profile image
thomsondavid

When integrating AspectJ with JUnit 5 testing in Gradle, ensure proper configuration of dependencies and plugins. charter spectrum internet provider Include AspectJ and JUnit 5 dependencies in the build. gradle file, and configure the AspectJ plugin to weave aspects into test classes. Implement test cases with JUnit 5 annotations, and apply aspects to target classes. Run tests using the Gradle test task to verify aspect behavior alongside JUnit 5 tests.