Skip to content
Chapter 06 · JUnit 5 & Mockito

Extensions ​

Chapters 4 and 5 used what JUnit offers out of the box. This chapter shows how to add your own behavior to tests.

An extension adds custom behavior to tests. With extensions you can:

  • enable or disable a test at run time,
  • run custom code before or after tests,
  • supply values for test parameters,
  • wrap a test method's call, for logging or timing,
  • handle the exceptions a test throws.

Extensions matter beyond your own code: much of JUnit itself is built on them (@Disabled is an execution condition), and so are the tools you will use with it. MockitoExtension (the Mockito chapters) and Spring's SpringExtension are both extensions.

Where extensions fit ​

The IDE or build tool calls the JUnit Platform; the Platform calls the Jupiter engine; Jupiter runs your tests. Between the engine's code and your test classes sits a chain of extensions: at fixed points while running a test, Jupiter calls every extension registered for it.

Every extension type is an interface that extends the marker interface Extension. You write a class that implements one or more of them, and register it with @ExtendWith on a test class or method. An extension that is not registered does nothing.

Each callback receives an ExtensionContext, which describes the test being run:

MethodGives
getTestClass()the test class
getTestMethod()the test method (empty at class level)
getTestInstance()the test object
getElement()the class or method, to read its annotations
getDisplayName()the display name
getConfigurationParameter(key)a value from junit-platform.properties

1. Lifecycle callbacks ​

The same hooks as @BeforeAll, @BeforeEach, @AfterEach and @AfterAll, written as interfaces:

InterfaceMethodRuns
BeforeAllCallbackbeforeAll(ExtensionContext)once, before all tests of the class
BeforeEachCallbackbeforeEach(ExtensionContext)before each test
AfterEachCallbackafterEach(ExtensionContext)after each test
AfterAllCallbackafterAll(ExtensionContext)once, after all tests of the class

Why, when the annotations exist? An annotated method belongs to one class: @BeforeAll in class A runs only for class A. An extension is a separate class that any number of test classes can reuse with one @ExtendWith; the logic lives in one place.

java
public class BeforeAllLifecycleExtension implements BeforeAllCallback {

    @Override
    public void beforeAll(ExtensionContext context) {
        System.out.println("Extension: Before all tests");
    }
}

public class AfterAllLifecycleExtension implements AfterAllCallback {

    @Override
    public void afterAll(ExtensionContext context) {
        System.out.println("Extension: After all tests");
    }
}

Several extensions are registered comma separated:

java
@ExtendWith({BeforeAllLifecycleExtension.class, AfterAllLifecycleExtension.class})
class MyServiceTest { ... }

Or one class can implement all four interfaces:

java
public class LifecycleExtension implements BeforeAllCallback, AfterAllCallback,
        BeforeEachCallback, AfterEachCallback {

    @Override
    public void beforeAll(ExtensionContext context) { System.out.println("Extension: Before all tests"); }

    @Override
    public void beforeEach(ExtensionContext context) { System.out.println("Extension: Before each test"); }

    @Override
    public void afterEach(ExtensionContext context) { System.out.println("Extension: After each test"); }

    @Override
    public void afterAll(ExtensionContext context) { System.out.println("Extension: After all tests"); }
}
java
@ExtendWith(LifecycleExtension.class)
class MyServiceTest {

    MyServiceTest() { System.out.println("instance created"); }

    @BeforeAll static void beforeAllMethod() { System.out.println("inside beforeAll"); }
    @BeforeEach void beforeEachMethod()      { System.out.println("inside beforeEach"); }
    @Test void testMethod1()                 { System.out.println("inside test method"); }
    @AfterEach void afterEachMethod()        { System.out.println("inside afterEach method"); }
    @AfterAll static void afterAllMethod()   { System.out.println("inside afterAll method"); }
}
text
Extension: Before all tests
inside beforeAll
instance created
Extension: Before each test
inside beforeEach
inside test method
inside afterEach method
Extension: After each test
inside afterAll method
Extension: After all tests

The rule: on the way in, the extension runs first; on the way out, the annotated method runs first. The extension wraps the annotated method.

2. Parameter resolver ​

Supplies values for parameters of test methods, constructors and lifecycle methods.

java
public class Student {
    String name;
    int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
java
@ExtendWith(StudentParameterResolver.class)
class MyServiceTest {

    @Test
    void testMethod1(Student student) {
        System.out.println("Student name is: " + student.name);   // Student name is: myName
    }
}

Who creates the Student? Without a resolver, JUnit fails the test with "No ParameterResolver registered for parameter ...". The resolver has two methods:

java
public class StudentParameterResolver implements ParameterResolver {

    @Override
    public boolean supportsParameter(ParameterContext parameterContext,
                                     ExtensionContext extensionContext) {
        return parameterContext.getParameter().getType() == Student.class;
    }

    @Override
    public Object resolveParameter(ParameterContext parameterContext,
                                   ExtensionContext extensionContext) {
        return new Student("myName", 27);
    }
}
  • supportsParameter: "can you supply this parameter?" Here: yes, if its type is Student.
  • resolveParameter: builds the value.

The ParameterContext describes one parameter: its position in the list, its type, its annotations, and the method or constructor that declares it.

For each parameter, Jupiter asks every registered resolver. Exactly one must support it: none gives "No ParameterResolver registered", and two or more fail the test with "Discovered multiple competing ParameterResolvers for parameter ...". Jupiter does not pick the first one.

JUnit has built-in resolvers for TestInfo, TestReporter and RepetitionInfo, which is why those parameters work without registering anything.

3. Execution condition ​

Decides at run time whether a test runs, using any custom logic. Say src/test/resources/junit-platform.properties contains:

properties
xyz.feature.enabled=false
java
public class ConditionalExecutionExtension implements ExecutionCondition {

    @Override
    public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
        boolean featureEnabled = Boolean.parseBoolean(
                context.getConfigurationParameter("xyz.feature.enabled").orElse("false"));

        return featureEnabled
                ? ConditionEvaluationResult.enabled("feature enabled, test can run")
                : ConditionEvaluationResult.disabled("feature disabled, test can not run");
    }
}

On one method, it affects only that method:

java
class MyServiceTest {

    @Test
    @ExtendWith(ConditionalExecutionExtension.class)
    void testMethod1() { System.out.println("inside testMethod1"); }   // skipped

    @Test
    void testMethod2() { System.out.println("inside testMethod2"); }   // runs
}

On the class, both are skipped (reported as disabled, with the reason). Unlike an assumption, a condition is checked before the test (or the class) starts: no instance is created and no lifecycle method runs.

4. Test execution callbacks ​

Run code immediately before and after the test method itself:

InterfaceRuns
BeforeTestExecutionCallbackafter all before-callbacks and @BeforeEach methods, just before the test
AfterTestExecutionCallbackjust after the test, before @AfterEach methods and after-callbacks

@BeforeEach and @AfterEach could do this, but their job is setup and cleanup (fresh values for each test). Timing or logging code does not belong there, and it would have to be copied into every class. An extension keeps it in one place.

A timing extension. The start time is kept in the extension context's Store, a per-test map for passing values between callbacks (an extension should keep no state in its own fields):

java
public class TimingExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback {

    @Override
    public void beforeTestExecution(ExtensionContext context) {
        context.getStore(ExtensionContext.Namespace.GLOBAL).put("start", System.nanoTime());
    }

    @Override
    public void afterTestExecution(ExtensionContext context) {
        long start = context.getStore(ExtensionContext.Namespace.GLOBAL).get("start", long.class);
        System.out.println(context.getDisplayName() + " took "
                + (System.nanoTime() - start) / 1_000_000 + " ms");
    }
}
java
@ExtendWith(TimingExtension.class)
class MyServiceTest {

    @BeforeEach void beforeEachMethod() { System.out.println("inside beforeEach method"); }
    @Test void testMethod1()            { System.out.println("inside testMethod1"); }
    @AfterEach void afterEachMethod()   { System.out.println("inside afterEach method"); }
}
text
inside beforeEach method
inside testMethod1
testMethod1() took 0 ms
inside afterEach method

5. Invocation interceptor ​

Wraps the call of the test method: pre-processing, the test, post-processing.

java
public class CustomInvocationExtension implements InvocationInterceptor {

    @Override
    public void interceptTestMethod(Invocation<Void> invocation,
                                    ReflectiveInvocationContext<Method> invocationContext,
                                    ExtensionContext extensionContext) throws Throwable {
        System.out.println("Pre-processing before test");
        invocation.proceed();                       // the test method runs here
        System.out.println("Post-processing after test");
    }
}

An interceptor must call proceed() exactly once (or skip()); otherwise JUnit fails the test. Registered on one method, it wraps only that method:

java
class MyServiceTest {

    @Test
    void testMethod1() { System.out.println("inside testMethod1"); }

    @Test
    @ExtendWith(CustomInvocationExtension.class)
    void testMethod2() { System.out.println("inside testMethod2"); }
}
text
inside testMethod1
Pre-processing before test
inside testMethod2
Post-processing after test

InvocationInterceptor has similar methods for other calls too (interceptBeforeEachMethod, interceptTestFactoryMethod, ...).

6. Exception handler ​

Intercepts exceptions thrown by the test method and decides what to do with them.

java
public class CustomExceptionHandler implements TestExecutionExceptionHandler {

    @Override
    public void handleTestExecutionException(ExtensionContext context, Throwable throwable)
            throws Throwable {
        System.out.println("Exception occurred in test: " + context.getDisplayName());
        System.out.println("Exception message: " + throwable.getMessage());
        throw throwable;     // rethrow: the test still fails
    }
}
java
@ExtendWith(CustomExceptionHandler.class)
class MyServiceTest {

    @Test
    void testMethod1() {
        System.out.println("inside testMethod1");
        int x = 1 / 0;
    }
}
text
inside testMethod1
Exception occurred in test: testMethod1()
Exception message: / by zero

What the handler does decides the result:

  • Rethrow it (or throw another exception): the test fails.
  • Return normally: the exception is swallowed and the test passes. Use this with care.

It only sees exceptions from the test method. An exception in @BeforeEach never reaches it (that needs LifecycleMethodExecutionExceptionHandler).

7. Test instance post-processor ​

Runs right after the test instance is created, before any before-each callback, lifecycle method or test: the place to initialize or inject fields of the test object.

java
public class CustomPostProcessorExtension implements TestInstancePostProcessor {

    @Override
    public void postProcessTestInstance(Object testInstance, ExtensionContext context) {
        if (testInstance instanceof MyServiceTest test) {
            test.value = 100;
        }
    }
}
java
@ExtendWith(CustomPostProcessorExtension.class)
class MyServiceTest {

    int value;

    @Test
    void testMethod1() {
        System.out.println("inside testMethod1: " + value);   // inside testMethod1: 100
    }
}

This is how frameworks inject things into tests: Mockito's extension creates the @Mock fields here.

Class level or method level ​

@ExtendWith on the class applies to every test in it; on a method, only to that test. Method level works for every per-test hook: conditions, parameter resolvers, before/after-each and test execution callbacks, interceptors, exception handlers and (with the default per-method lifecycle) instance post-processors. Only the class-wide hooks make no sense there: a BeforeAllCallback or AfterAllCallback registered on a method is never called.

Other ways to register ​

java
class MyServiceTest {

    @RegisterExtension
    static TimingExtension timing = new TimingExtension();   // programmatic
}
  • @RegisterExtension on a field registers an instance you build yourself, so it can be configured through its constructor. Use a static field for class-level callbacks (BeforeAllCallback, ...).
  • Automatic registration: list the class in META-INF/services/org.junit.jupiter.api.extension.Extension and set junit.jupiter.extensions.autodetection.enabled=true. It then applies to every test, without @ExtendWith.

With several extensions of the same kind, "before" callbacks run in registration order and "after" callbacks in reverse order, so the first extension wraps the others.

The order of everything ​

A simplified view of what the Jupiter engine does for one test class (default per-method lifecycle). The numbers match the order verified by running a class with all seven extension types:

java
void executeTestClass(Class<?> testClass) {
    // 0. ExecutionCondition: is the class enabled?
    // 1. BeforeAllCallback extensions, then @BeforeAll methods

    for (Method testMethod : testMethods) {
        // 0. ExecutionCondition: is this test enabled?
        executeTestMethod(testMethod);
    }

    // 10. @AfterAll methods, then AfterAllCallback extensions
}

void executeTestMethod(Method testMethod) {
    Object testInstance = createTestInstance();          // constructor
    // 2. TestInstancePostProcessor
    try {
        // 3. BeforeEachCallback extensions, then @BeforeEach methods
        // 4. BeforeTestExecutionCallback
        try {
            // 5. ParameterResolver: resolve each parameter (exactly one resolver per parameter)
            // 6. InvocationInterceptor wraps the call: pre, testMethod.invoke(...), post
        } catch (Throwable t) {
            // 7. TestExecutionExceptionHandler (rethrow = fail, return = pass)
        }
        // 8. AfterTestExecutionCallback
    } finally {
        // 9. @AfterEach methods, then AfterEachCallback extensions
    }
}

Two details differ from what one might guess: parameters are resolved after BeforeTestExecutionCallback, right before the call; and the exception handler runs before AfterTestExecutionCallback, which still runs after a failure, like the after-each hooks.

If a @BeforeEach method throws, the test fails at once: BeforeTestExecutionCallback, the test and the exception handler are skipped, while the after-each callbacks still run.

Next: The next chapters run one test method more than once. Chapter 7 starts with the simplest case: @RepeatedTest, the same test with the same input, several times.

Check yourself ​

  1. Why use a BeforeEachCallback extension when @BeforeEach exists?
  2. With both registered, which runs first: BeforeAllCallback or @BeforeAll? @AfterEach or AfterEachCallback?
  3. What does ExtensionContext give an extension?
  4. What are the two methods of a ParameterResolver, and what happens when two resolvers support the same parameter?
  5. How does an ExecutionCondition differ from an assumption?
  6. Why put timing code in a BeforeTestExecutionCallback rather than @BeforeEach?
  7. What must an InvocationInterceptor do with the invocation?
  8. A TestExecutionExceptionHandler returns without rethrowing. What is the test's result?
  9. When does a TestInstancePostProcessor run, and what is it used for?
  10. What happens when a BeforeAllCallback is registered on a test method?
  11. Name two ways to register an extension other than @ExtendWith.
Answers
  1. @BeforeEach belongs to one class; an extension is reusable across many classes with one annotation.
  2. BeforeAllCallback first; @AfterEach first. Extensions wrap the annotated methods.
  3. The test class, method, instance, element (annotations), display name, configuration parameters, and a Store for values.
  4. supportsParameter and resolveParameter. The test fails: "Discovered multiple competing ParameterResolvers".
  5. A condition is evaluated before the test starts (no instance, no lifecycle methods) and reports the test as disabled; an assumption runs inside the test and aborts it.
  6. @BeforeEach is for setup and cleanup, and is per class; the callback keeps timing separate and reusable.
  7. Call proceed() exactly once (or skip()).
  8. It passes: the exception is swallowed.
  9. Right after the test instance is created, before any before-each hook; to initialize or inject fields of the test object.
  10. Nothing: it is never called. Class-wide hooks only work when registered on the class.
  11. @RegisterExtension on a field, and automatic registration through META-INF/services with autodetection enabled.