-
Notifications
You must be signed in to change notification settings - Fork 38.9k
Closed as not planned
Closed as not planned
Copy link
Labels
in: testIssues in the test moduleIssues in the test modulestatus: invalidAn issue that we don't feel is validAn issue that we don't feel is valid
Description
I implemented a TemplateSpringExtension which extends SpringExtension to separate Spring context per JUnit Jupiter ClassTemplate parameter and still benefit from cached contexts.
It works because I can update the JUnit Jupiter 5 ExtensionContext.Store with prepared context like below and because SpringExtension uses store.getOrComputeIfAbsent which won't have an effect as TestContextManager is already defined by TemplateSpringExtension.
private static final ExtensionContext.Namespace TEST_CONTEXT_MANAGER_NAMESPACE =
ExtensionContext.Namespace.create(SpringExtension.class);
private void prepareTemplateInvocation(ExtensionContext context) {
Class<?> testClass = context.getRequiredTestClass();
ExtensionContext.Store store = context.getRoot().getStore(TEST_CONTEXT_MANAGER_NAMESPACE);
store.remove(testClass);
store.put(testClass, new TestContextManager(getBootstraper(testClass)));
}Can I assume that it won't break with future Spring updates?
Full code looks like:
public class TemplateSpringExtension extends SpringExtension {
private final TemplateContext templateContext;
public record TemplateContext(String displayName, Object param, String... properties) {}
public interface WithTemplateParameter {
void withTemplateParameter(Object value);
}
public static ClassTemplateInvocationContext getClassTemplateInvocationContext(
TemplateContext templateContext) {
return new InvocationContext(templateContext);
}
@SuppressWarnings("unused")
public TemplateSpringExtension() {
this(null);
}
public TemplateSpringExtension(TemplateContext templateContext) {
this.templateContext = templateContext;
}
private static final ExtensionContext.Namespace TEST_CONTEXT_MANAGER_NAMESPACE =
ExtensionContext.Namespace.create(SpringExtension.class);
private void prepareTemplateInvocation(ExtensionContext context) {
Class<?> testClass = context.getRequiredTestClass();
ExtensionContext.Store store = context.getRoot().getStore(TEST_CONTEXT_MANAGER_NAMESPACE);
store.remove(testClass);
store.put(testClass, new TestContextManager(getBootstrapper(testClass)));
}
/**
* Similar to what BootstrapUtils.resolveTestContextBootstrapper(testClass) but with support for
* class template parameter.
*
* <p>Name of {@link TestContextBootstrapper} is part of the {@link MergedContextConfiguration} so
* We return {@link SpringBootTestContextBootstrapper} used by default by SpringBoot if there are
* no custom properties.
*
* <p>Otherwise, we return bootstrapper with properties customized by test template.
*/
private TestContextBootstrapper getBootstrapper(Class<?> testClass) {
DefaultCacheAwareContextLoaderDelegate contextLoader =
new DefaultCacheAwareContextLoaderDelegate();
DefaultBootstrapContext bootstrapContext =
new DefaultBootstrapContext(testClass, contextLoader);
String[] contextProperties = getContextProperties();
TestContextBootstrapper bootstrapper =
contextProperties != null
? new Bootstrapper(contextProperties)
: new SpringBootTestContextBootstrapper();
bootstrapper.setBootstrapContext(bootstrapContext);
return bootstrapper;
}
private String[] getContextProperties() {
if (templateContext != null
&& templateContext.properties() != null
&& templateContext.properties().length > 0) {
return templateContext.properties();
}
return null;
}
private static class Bootstrapper extends SpringBootTestContextBootstrapper {
private final String[] contextProperties;
public Bootstrapper(String... contextProperties) {
this.contextProperties = contextProperties;
}
@Override
protected String[] getProperties(Class<?> testClass) {
return contextProperties;
}
}
private static class InvocationContext implements ClassTemplateInvocationContext {
private final TemplateContext templateContext;
private final TemplateSpringExtension envExtension;
private InvocationContext(TemplateContext templateContext) {
this.templateContext = templateContext;
this.envExtension = new TemplateSpringExtension(templateContext);
}
@Override
public String getDisplayName(int invocationIndex) {
return templateContext.displayName();
}
@Override
public void prepareInvocation(ExtensionContext context) {
envExtension.prepareTemplateInvocation(context);
}
@Override
public List<Extension> getAdditionalExtensions() {
List<Extension> result = new ArrayList<>();
result.add(envExtension);
TestInstancePostProcessor testInstancePostProcessor =
(testInstance, context) -> {
if (testInstance instanceof WithTemplateParameter withTestParam) {
withTestParam.withTemplateParameter(templateContext.param());
}
};
result.add(testInstancePostProcessor);
return result;
}
}
}Metadata
Metadata
Assignees
Labels
in: testIssues in the test moduleIssues in the test modulestatus: invalidAn issue that we don't feel is validAn issue that we don't feel is valid