Lifecycle of JUnit 5 Extension Model
JUnit5 final release is around the corner (currently it is M4), and I have started playing with it a bit on how to write extensions.
In JUnit5, instead of dealing with Runners, Rules, ClassRules and so on, you’ve got a single Extension API to implement your own extensions.
JUnit5 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 at http://junit.org/junit5/docs/current/user-guide/#extensions-lifecycle-callbacks
But in which point of the process it is executed each of them? To test it I have just created an extension that implements all interfaces and each method prints who is it.
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 JUnit5 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> 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. 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> Second test contains requires a parameter resolution. Parameter resolvers are run after Before events and before executing the test itself.
<4> Last test throws an exception. Test Execution Exception is called after test is executed but before After events.
Last thing to notice is that BeforeAll and AfterAll events are executed per test class and not suite.
The JUnit version used in this example is org.junit.jupiter:junit-jupiter-api:5.0.0-M4
Reference: | Lifecycle of JUnit 5 Extension Model from our JCG partner Alex Soto at the One Jar To Rule Them All blog. |