How to simulate real BeforeAll and AfterAll in JUnit 5

How to simulate real BeforeAll and AfterAll in JUnit 5

Introduction

JUnit 5 is a well-known Java Testing Framework/Library across developers. It’s the evolution of JUnit 4 and carries with it a lot of awesome features. One of the most important ones is setting pre and post-conditions as knowing by the terms Before (pre-condition) and After (post-condition).

It has 2 supported ways: Before/After All and Before/After Each.
The “All” part means that a code block can be executed as pre or post-condition before or after it can initialize all tests. The “Each” part means that a code block can be executed as a pre or post-condition before or after each test.

The JUnit 5 official docs say the following about these strategies, which are annotations:

Annotation
Description

@BeforeEach
Denotes that the annotated method should be executed before each @Test, @RepeatedTest, @ParameterizedTest, or @TestFactory method in the current class; analogous to JUnit 4’s @Before. Such methods are inherited unless they are overridden

@AfterEach
Denotes that the annotated method should be executed after each @Test, @RepeatedTest, @ParameterizedTest, or @TestFactory method in the current class; analogous to JUnit 4’s @After. Such methods are inherited unless they are overridden.

@BeforeAll
Denotes that the annotated method should be executed before all @Test, @RepeatedTest, @ParameterizedTest, and @TestFactory methods in the current class; analogous to JUnit 4’s @BeforeClass. Such methods are inherited unless they are overridden and must be static unless the “per-class” test instance lifecycle is used.

@AfterAll
Denotes that the annotated method should be executed after all @Test, @RepeatedTest, @ParameterizedTest, and @TestFactory methods in the current class; analogous to JUnit 4’s @AfterClass. Such methods are inherited unless they are overridden and must be static unless the “per-class” test instance lifecycle is used.

What problem we are trying to solve

Only by looking at the annotations, we understand that it covers most of the scenarios we want for our tests.
We have the @BeforeAll and @AfterAll annotations as a general pre or post-condition.

One common testing pattern applied in good testing architectures is the BaseTest class: a place where we can add pre and post-conditions that will shared across different tests by inheritance. Normally, we want to control these conditions in different ways. Developers have different ways to control it through the BaseTest pattern:

open the browser only once when any test starts to save time
keep a container (Testcontainer) opened for different test classes
ingest data before any test is executed and remove it after all are executed

I have bad news: JUnit 5 doesn’t have a way to control the three mentioned scenarios as the @BeforeAll and @AfterAll are executed per test instance, meaning per test class. Other frameworks like TestNG have this ability called @BeforeSuite and @AfterSuite, and it’s exactly what we want that JUnit 5 does not support.

Let’s understand how we could use JUnit 5 for this and how to fix this problem.

The BeforeAllCallback and AfterAllCallback interfaces

You might do some Google search, like I did infinite times, encountering the BeforeAllCallback and AfterAllCallback interfaces which are Extensions of Testing Lifecycle Callbacks. It seems a good solution as these interfaces enable you to run the @BeforeAll or @AfterAll.

public class MyExtension implements BeforeAllCallback, AfterAllCallback {
@Override
public void afterAll(ExtensionContext context) {
// pre general condition
}

@Override
public void beforeAll(ExtensionContext context) {
// post general conditions
}
}

The UML diagram shows a BaseTest using a JUnit 5 Extension called MyExtension that implements the BeforeAllCallback and AfterAllCallback. The base test is used in the FeatureTest1 and FeatureTest2. Note that the MyExtension has the beforeAll and afterAllMethods, as well as the BaseTest has it.

It solves the problem as the MyExtension would serve as the global before and after, as the ones in the BaseTest would run per test instance, meaning running when Feature1Test and Feature2Test run. Unfortunately, it’s not the case. If we would add only a System.out.println() call in each method, the output would be the following:

[INFO] ——————————————————-
[INFO] T E S T S
[INFO] ——————————————————-
[INFO] Running com.eliasnogueira.feature1.Feature1Test
MyExtension.beforeAll
BaseTest.beforeAll
Feature1Test.test1
Feature1Test.test2
Feature1Test.test3
BaseTest.afterAll
MyExtension.afterAll
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.018 s — in com.eliasnogueira.feature1.Feature1Test

[INFO] Running com.eliasnogueira.feature2.Feature2Test
MyExtension.beforeAll
BaseTest.beforeAll
Feature2Test.test1
Feature1Test.test2
BaseTest.afterAll
MyExtension.afterAll
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s — in com.eliasnogueira.feature2.Feature2Test

You can see that the methods in the MyExtension run for every test class, as well as the BaseTest. This means that JUnit runs it per test instance. Unfortunately, JUnit 5 doesn’t have a solution for a general precondition before or after any test for the whole test execution.

Want to see it in action?
– Clone this repo: git clone https://github.com/eliasnogueira/junit5-before-after-all
– Switch to the no-solution branch
– Run mvn test

How to solve it

There is a light at the end of the tunnel and it’s not difficult. Like everyone, I Google it and found an interesting workaround on this Stackoverflow thread: https://stackoverflow.com/questions/43282798/in-junit-5-how-to-run-code-before-all-test.

Be aware that this is a workaround and might not work in future JUnit versions.

The solution is based on using the BeforeAllCallback interface, with a thread lock in case parallel tests run to solve the general precondition and the JUnit storage mechanism to mimic the postcondition using the ExtensionContext.Store.CloseableResource interface. Don’t worry, I will break down the implementation.

The example

It is a simple one just to show you that the approach works. The example shows a general BaseTest and a BaseTest per feature, where an extension will be created to give the ability of execution a general pre and postcondition.

The extension implementation

The implementation can be done in four steps, and the final solution will look like this:

1 public class SetupExtension implements BeforeAllCallback, Store.CloseableResource {
2
3 private static final Lock LOCK = new ReentrantLock();
4 private static volatile boolean started = false;
5
6 @Override
7 public void beforeAll(ExtensionContext context) {
8 LOCK.lock();
9
10 try {
11 if (!started) {
12 started = true;
13 System.out.println(“[pre-condition] Should run only once”);
14 context.getRoot().getStore(Namespace.GLOBAL).put(“Placeholder for post condition”, this);
15 }
16 } finally {
17 // free the access
18 LOCK.unlock();
19 }
20 }
21
22
23 @Override
24 public void close() {
25 System.out.println(“[post-condition] Should run only once”);
26 }
27 }

Implementation of the necessary interfaces

Line 1 shows that the two interfaces: BeforeAllCallback overriding the beforeAll() method which will control the general precondition and ExtensionContext.Store.CloseableResource overrides the close() method which will mimic the general postcondition.

Controlling the single execution of the beforeAll

To guarantee that it will be executed only once one strategy must be applied: control that it has started, so the beforeAll() won’t execute again.

Line 8 shows that we are locking the thread. This is necessary to ensure that any parallel execution will be possible. Line 11 checks if the code had any previous execution. When it’s the first time the boolean started is set as true ensure it won’t go to the code block in the subsequent runs. The finally section unlocks the thread.

Implementing the general precondition

Any necessary implementation for the general precondition should be placed inside the if condition, simple like that. We can see this in the line 13.

Add a signal (storage) to mimic the general postcondition

The way to mimic the general postcondition here is through the Store. In JUnit 5, we can store objects for later retrieval in an extension and it can be done using the getStore(context).put(key, value) where the context is the root or current context and the key,value are the key and value to add to its storage.

Line 14 creates a dummy store object for later usage in the automatic close() method invocation.

Implement the general postcondition

The close() method from the ExtensionContext.Store.CloseableResource interface will be invoked when the extension lifecycle ends [reference]. This is the last opportunity to execute any code before the program exits. In this way, we can simulate the general postcondition.

Code example

The https://github.com/eliasnogueira/junit5-before-after-all project shows the implementation based on this article’s explanation matching the diagram in “The example” section.

While running the tests you will see the following output:

[INFO] ——————————————————-
[INFO] T E S T S
[INFO] ——————————————————-
[INFO] Running com.eliasnogueira.feature1.Feature1Test
[pre-condition] Should run only once
BaseTest.beforeAll
BaseFeature1Test.beforeAll
Feature1Test.test1
Feature1Test.test2
Feature1Test.test3
BaseFeature1Test.afterAll
BaseTest.afterAll
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.019 s — in com.eliasnogueira.feature1.Feature1Test

[INFO] Running com.eliasnogueira.feature2.Feature2Test
BaseTest.beforeAll
BaseFeature2Test.beforeAll
Feature2Test.test1
Feature1Test.test2
BaseFeature2Test.afterAll
BaseTest.afterAll
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s — in com.eliasnogueira.feature2.Feature2Test
[post-condition] Should run only once

Note that the general precondition is the text output as [pre-condition] Should run only once and the general postcondition is the output as [post-condition] Should run only once. You can see them at the beginning and the end of all test executions, respectively.

Using this simple approach you can have the general pre and postcondition in your code.
Happy tests!