Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Lifecycle of JUnit 5's Extension Model

DZone's Guide to

Lifecycle of JUnit 5's Extension Model

JUnit 5 is drawing near, so it's time to look at its new Extension API. Here, we see how to us it and determine exactly when tests are run on your code.

· Java Zone
Free Resource

Try Okta to add social login, MFA, and OpenID Connect support to your Java app in minutes. Create a free developer account today and never build auth again.

JUnit 5's final release is around the corner (currently it is M4), and I have started playing with it a bit to see how to write extensions.

In JUnit 5, instead of dealing with RunnersRulesClassRules and so on, you've got a single Extension API to implement your own extensions.

JUnit 5 provides several interfaces to hook in its lifecycle. For example, you can hook to Test Instance Post-processing to invoke custom initialization methods on the test instance, or Parameter Resolution for dynamically resolving test method parameters at runtime. And, of course, the typical ones like hooking before all tests are executed, before a test is executed, after a test is executed, and so on so far. A complete list can be found here.

But at which point of the process is each of them executed? To test it, I have just created an extension that implements all interfaces, and each method prints who it is.

public class LoggerExtension implements TestInstancePostProcessor, ParameterResolver, BeforeAllCallback,
    BeforeEachCallback, BeforeTestExecutionCallback, AfterEachCallback, AfterTestExecutionCallback, AfterAllCallback,
    TestExecutionExceptionHandler {
    @Override
    public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception {
        System.out.println("Test Instance Post-processing called");
    }

    @Override
    public boolean supports(ParameterContext parameterContext, ExtensionContext extensionContext)
        throws ParameterResolutionException {
        System.out.println("Parameter Resolver Supports called");
        return parameterContext.getParameter().getType().equals(String.class);
    }

    @Override
    public Object resolve(ParameterContext parameterContext, ExtensionContext extensionContext)
        throws ParameterResolutionException {
        System.out.println("Resolver called");
        return "Hello World";
    }

    @Override
    public void beforeAll(ContainerExtensionContext context) throws Exception {
        System.out.println("Before All called " + context.getTestClass().get());
    }

    @Override
    public void beforeEach(TestExtensionContext context) throws Exception {
        System.out.println("Before Each called");
    }

    @Override
    public void beforeTestExecution(TestExtensionContext context) throws Exception {
        System.out.println("Before Test Execution called");
    }

    @Override
    public void afterEach(TestExtensionContext context) throws Exception {
        System.out.println("After Each called");
    }

    @Override
    public void afterTestExecution(TestExtensionContext context) throws Exception {
        System.out.println("After Test Executon called");
    }

    @Override
    public void afterAll(ContainerExtensionContext context) throws Exception {
        System.out.println("After All called");
    }

    @Override
    public void handleTestExecutionException(TestExtensionContext context, Throwable throwable) throws Throwable {
        System.out.println("Test Execution Exception called");
        throw throwable;
    }
}


Then I have created a JUnit 5 test suite containing two tests:

@ExtendWith(LoggerExtension.class)
public class AnotherLoggerExtensionTest {

    @Test
    public void test4() {
        System.out.println("Test 4");
    }

}


@ExtendWith(LoggerExtension.class)
public class LoggerExtensionTest {

    @Test
    public void test1() {
        System.out.println("Test 1");
    }

    @Test
    public void test2(String msg) {
        System.out.println("Test 2 " + msg);
    }

    @Test
    public void test3() {
        System.out.println("Test 3");
        throw new IllegalArgumentException("");
    }

}


@RunWith(JUnitPlatform.class)
@SelectClasses({LoggerExtensionTest.class, AnotherLoggerExtensionTest.class})
public class LoggerExtensionTestSuite {

}


So after executing this suite, what it is the output? Let's see it. Notice that for sake of readability I have added some callouts on terminal output.

Before All called class AnotherLoggerExtensionTest
Test Instance Post-processing called
Before Each called
Before Test Execution called
Test 4
After Test Execution called
After Each called
After All called

// <1>

Before All called class LoggerExtensionTest
Test Instance Post-processing called
Before Each called
Before Test Execution called
Test 1
After Test Execution called
After Each called

// <2>

Test Instance Post-processing called
Before Each called
Before Test Execution called
Parameter Resolver Supports called
Resolver called
Test 2 Hello World
After Test Execution called
After Each called

// <3>

Test Instance Post-processing called
Before Each called
Before Test Execution called
Test 3
Test Execution Exception called
After Test Execution called
After Each called

// <4>

After All called


  • <1>: The first test that it is run is AnotherLoggerExtensionTest. In this case, there is only one simple test, so the lifecycle of extension is BeforeAllTest Instance-Post-ProcessingBefore EachBefore Test Execution, then the test itself is executed, and then all After callbacks.

  • <2>: Then the LoggerExtensionTest is executed. The first test is not a parametrized test, so events related to parameter resolution are not called. Before the test method is executed, test instance post-processing is called, and after that, all before events are thrown. Finally, the test is executed with all after events.

  • <3>: The second test contains requires a parameter resolution. Parameter resolvers are run after Before events and before executing the test itself.

  • <4>: The last test throws an exception. Test Execution Exception is called after the test is executed but before After events.

The last thing to notice is that BeforeAll and AfterAll events are executed per test class and not per suite.

The JUnit version used in this example is org.junit.jupiter:junit-jupiter-api:5.0.0-M4.

Build and launch faster with Okta’s user management API. Register today for the free forever developer edition!

Topics:
java ,junit 5 ,extension api ,lifecycle ,tutorial

Published at DZone with permission of Alex Soto, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}