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.
Join the DZone community and get the full member experience.
Join For FreeJUnit 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 Runners, Rules, ClassRules 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 {
public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception {
System.out.println("Test Instance Post-processing called");
}
public boolean supports(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
System.out.println("Parameter Resolver Supports called");
return parameterContext.getParameter().getType().equals(String.class);
}
public Object resolve(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
System.out.println("Resolver called");
return "Hello World";
}
public void beforeAll(ContainerExtensionContext context) throws Exception {
System.out.println("Before All called " + context.getTestClass().get());
}
public void beforeEach(TestExtensionContext context) throws Exception {
System.out.println("Before Each called");
}
public void beforeTestExecution(TestExtensionContext context) throws Exception {
System.out.println("Before Test Execution called");
}
public void afterEach(TestExtensionContext context) throws Exception {
System.out.println("After Each called");
}
public void afterTestExecution(TestExtensionContext context) throws Exception {
System.out.println("After Test Executon called");
}
public void afterAll(ContainerExtensionContext context) throws Exception {
System.out.println("After All called");
}
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:
LoggerExtension.class) (
public class AnotherLoggerExtensionTest {
public void test4() {
System.out.println("Test 4");
}
}
LoggerExtension.class) (
public class LoggerExtensionTest {
public void test1() {
System.out.println("Test 1");
}
public void test2(String msg) {
System.out.println("Test 2 " + msg);
}
public void test3() {
System.out.println("Test 3");
throw new IllegalArgumentException("");
}
}
JUnitPlatform.class) (
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 BeforeAll, Test Instance-Post-Processing, Before Each, Before 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.
Published at DZone with permission of Alex Soto, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments