In most cases when I consider creating an extension, I run into the same problem: How to store mutable objects? Here is my problem: Let's consider that my extension provides a parameter resolver which provides a mutable object to the test. Let's say the object has methods to change the configuration. A naive implementation based on the JUnit 5 User Guide and Javadoc could look like this:
public class MyExtension implements ParameterResolver {
private static final Namespace NAMESPACE = Namespace.create(MyExtension.class);
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return parameterContext.getParameter().getType() == MyMutableType.class;
}
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return extensionContext.getStore(NAMESPACE).getOrComputeIfAbsent(MyMutableType.class, __ -> new MyMutableType());
}
}
Unfortunately, this implementation is broken for the following test class.
@ExtendWith(MyExtension.class)
final class MyTest {
@BeforeAll
static void init(MyMutableType resolvedObject) {
}
@Test
void test1(MyMutableType resolvedObject) {
resolvedObject.changeSomeConfig();
...
}
@Test
void test2(MyMutableType resolvedObject) {
// resolvedObject might be affected by changed configuration in test1().
...
}
}
Till this day, I couldn't find a good solution. Is there a guide by JUnit how this is supposed to work. I couldn't find anything. To solve this issue, I see two approaches. Neither of them seems to work well with JUnit's API.
One approach is to forbid using my parameter resolver when the ExtensionContext
is not specific to a single test. Unfortunately, I couldn't find any reliable way to check that. I can check for annotations like @BeforeAll but that is more an estimation then a reliable check.
The second approach is to copy the object when we enter a more specific ExtensionContext
. Alternatively, I could set a flag which prevents further modification and provides meaningfull error messages. However, such implementation is far from straightforward and looks more like misusing the API. Beside that, the implementation might be too strict by using the copyOperator
when not actually necessary.
<T> T getOrCompute(ExtensionContext extensionContext, Object key, Supplier<T> factory, UnaryOperator<T> copyOperator) {
Store store = extensionContext.getStore(NAMESPACE);
// Using remove() because it is the only method ignoring ancestors
Object previous = store.remove(key);
if (previous == null) {
T fromAncestor = (T) store.get(key);
if (fromAncestor == null) {
T result = factory.get();
store.put(key, result);
return result;
}
else {
T result = copyOperator.apply(fromAncestor);
store.put(key, result);
return result;
}
}
else {
store.put(key, previous);
return (T) previous;
}
}
I'm wondering if I'm missing something important or if JUnit just doesn't have a meaningful way to handle mutable state in extensions.