Problem Statement
When running JUnit5 tests using Gradle in Java, I wanted to log the arguments, that a method was receiving. One way was to put logger just before the method is executed. However, there are 2 challenges:
- If the method is being used 100 times, we have to add the logger in all 100 places.
- 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 assertj-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();
}
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
aspectjlibrary 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"
}
Add aspectj dependencies
dependencies {
implementation "org.aspectj:aspectjrt:1.9.22"
implementation "org.aspectj:aspectjweaver:1.9.22"
// Other dependencies
}
Add following snippet
compileTestJava {
ajc {
options {
aspectpath.setFrom configurations.aspect
compilerArgs = ["-Xlint:ignore", "-Xajruntimetarget:1.5"]
}
}
}
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);
}
}
}
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
}
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!
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)
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.