Maciej Walkowiak

Auto-Registering JUnit 5 extensions

Russia has invaded Ukraine and already killed tens of thousands of civilians, with many more raped and tortured. Ukraine needs your help!

Help Ukraine Now!

In rare cases you may want to register certain extension for each test in your test suite. A typical use case that comes to my mind is benchmarking or tracing (look JUnit OpenTelemetry Extension).

A simple but tedious approach is to decorate each test class with @ExtendWith(...). I don't think anyone would have such task especially in a project with large number of test classes. Fortunately, there is a better, less invasive way.

For the sake of example let's create an extension that will measure the time it takes to execute a test:

For the sake of simplicity I am using StopWatch class from Spring Framework to measure execution time.
package com.example.junit;

import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;

import org.springframework.util.StopWatch;

public class BenchmarkExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback {

    @Override
    public void beforeTestExecution(ExtensionContext context) {
        var stopWatch = new StopWatch();
        context.getStore(Namespace.create(BenchmarkExtension.class, context.getRequiredTestMethod()))
                .put("stopWatch", stopWatch);
        stopWatch.start();
    }

    @Override
    public void afterTestExecution(ExtensionContext context) {
        var testMethod = context.getRequiredTestMethod();
        var stopWatch = context.getStore(Namespace.create(BenchmarkExtension.class, testMethod))
                .get("stopWatch", StopWatch.class);
        stopWatch.stop();
        context.publishReportEntry(
                String.format("Method %s#%s executed in %d ms", context.getRequiredTestClass().getName(), testMethod.getName(),
                        stopWatch.getLastTaskTimeMillis()));
    }
}

Instead of using @ExtendWith, we will use JUnit Service Loader capabilities to register extension and then enable extension auto-detection:

  1. Create a file src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension with content:
com.example.junit.BenchmarkExtension
  1. Enable extensions auto-detection in src/test/resources/junit-platform.properties:
junit.jupiter.extensions.autodetection.enabled=true

Note, that this enables auto-detection for all extensions registered with service loader in your classpath.

Now, when we run tests, at the end of each test following a statement similar to this one will be printed to the console:

timestamp = 2022-07-26T18:39:56.899219, value = Method com.example.MyService#testSlowMethod executed in 137 ms

While it is good to know that such feature exists, please do not abuse it - it is good to know which extensions are loaded just by looking at the test class.