Vishal Bhavsar
Posted on March 31, 2024
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
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"
}
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
Posted on March 31, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.