-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Description
Version info
The problem occurs in 6.0.1 version. It used to work fine in 5.10.2 but then regressed in 5.11.0. A partial fix was implemented in #3944.
Steps to reproduce
Given below test:
package org.example.somepackage;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.*;
import static org.junit.jupiter.api.extension.ExtensionContext.*;
@ExtendWith(ExampleTest.ExampleExtension.class)
class ExampleTest {
@Test
void test() {
assert false;
}
static class ExampleExtension implements BeforeEachCallback, AfterEachCallback, TestWatcher {
private static final Namespace NAMESPACE = Namespace.create(ExampleExtension.class);
@Override
public void beforeEach(ExtensionContext context) {
getStore(context).put("value", new ExtensionState("enigma"));
}
@Override
public void afterEach(ExtensionContext context) throws Exception {
System.out.println("afterEach: " + getStore(context).get("value"));
}
@Override
public void testFailed(ExtensionContext context, Throwable cause) {
System.out.println("testFailed: " + getStore(context).get("value"));
}
private static Store getStore(ExtensionContext context) {
return context.getStore(NAMESPACE);
}
}
private static class ExtensionState implements AutoCloseable {
private String someString;
public ExtensionState(String someString) {
this.someString = someString;
}
@Override
public void close() {
someString = "DISPOSED";
}
@Override
public String toString() {
return "someString is '%s'".formatted(someString);
}
}
}we get the following output (6.0.1):
afterEach: someString is 'ready to go'
testFailed: someString is 'DISPOSED'
with 5.10.2 it is:
afterEach: someString is 'ready to go'
testFailed: someString is 'ready to go'
with 5.11.0 it is:
afterEach: someString is 'ready to go'
Nov 12, 2025 9:52:37 PM org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor lambda$invokeTestWatchers$3
WARNING: Failed to invoke TestWatcher [org.example.somepackage.ExampleTest$ExampleExtension] for method [org.example.somepackage.ExampleTest#test()] with display name [test()]
org.junit.jupiter.api.extension.ExtensionContextException: A NamespacedHierarchicalStore cannot be modified or queried after it has been closed
Expected result
The expected result is that the TestWatcher callbacks can access ExtensionContext#Store before it is disposed - as it was in 5.10.2.
I can see that there was a shot at fixing this problem in #3944 but it was only partially successful.
The other problem is that we cannot remove items from the store in TestWatcher callbacks because we get A NamespacedHierarchicalStore cannot be modified or queried after it has been closed.
Real life scenario
I have an extension which sets up Selenium's WebDriver in BeforeEachCallback. After the tests are done with the WebDriver I destroy it TestWatcher callbacks. In case a test failed I need to gather some additional information (e.g. screenshot, some dumps, etc.) before the cleanup. It used to work before 5.11.0 and now it's impossible to do correctly IMO.
The workaround I have for now is to move the cleanup code to the AfterEachCallback. The problem is that in AfterEach we do not know if the test failed or not. This is my current workaround but it seems fragile and doesn't convince me:
@Override
public void afterEach(@Nonnull ExtensionContext context) {
var testFailed = context.getExecutionException().isPresent();
if (testFailed) {
gatherInformation(context);
}
cleanup(context);
}