Allow directly present @BootstrapWith to override meta annotations
Prior to this commit, if a test class was meta-annotated with multiple @BootstrapWith declarations that registered different TestContextBootstrapper implementations, such a configuration would result in an IllegalStateException, and there was no way to override this behavior. This commit addresses this shortcoming by relaxing the explicit TestContextBootstrapper resolution in BootstrapUtils so that a directly present @BootstrapWith annotation will now override declarations of @BootstrapWith that are meta-present. In other words, if @BootstrapWith is used as a meta-annotation, it can be overridden directly on the test class via an explicit, local declaration of @BootstrapWith. Issue: SPR-17006
This commit is contained in:
@@ -26,7 +26,6 @@ import org.springframework.beans.BeanUtils;
|
|||||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||||
import org.springframework.core.annotation.AnnotationAttributes;
|
import org.springframework.core.annotation.AnnotationAttributes;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -154,10 +153,19 @@ abstract class BootstrapUtils {
|
|||||||
if (annotations.isEmpty()) {
|
if (annotations.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Assert.state(annotations.size() <= 1, () -> String.format(
|
if (annotations.size() == 1) {
|
||||||
|
return annotations.iterator().next().value();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow directly-present annotation to override annotations that are meta-present.
|
||||||
|
BootstrapWith bootstrapWith = testClass.getDeclaredAnnotation(BootstrapWith.class);
|
||||||
|
if (bootstrapWith != null) {
|
||||||
|
return bootstrapWith.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalStateException(String.format(
|
||||||
"Configuration error: found multiple declarations of @BootstrapWith for test class [%s]: %s",
|
"Configuration error: found multiple declarations of @BootstrapWith for test class [%s]: %s",
|
||||||
testClass.getName(), annotations));
|
testClass.getName(), annotations));
|
||||||
return annotations.iterator().next().value();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Class<?> resolveDefaultTestContextBootstrapper(Class<?> testClass) throws Exception {
|
private static Class<?> resolveDefaultTestContextBootstrapper(Class<?> testClass) throws Exception {
|
||||||
|
|||||||
@@ -99,6 +99,14 @@ public class BootstrapUtilsTests {
|
|||||||
assertBootstrapper(DuplicateMetaAnnotatedBootstrapWithAnnotationClass.class, FooBootstrapper.class);
|
assertBootstrapper(DuplicateMetaAnnotatedBootstrapWithAnnotationClass.class, FooBootstrapper.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 5.1
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void resolveTestContextBootstrapperWithLocalDeclarationThatOverridesMetaBootstrapWithAnnotations() {
|
||||||
|
assertBootstrapper(LocalDeclarationAndMetaAnnotatedBootstrapWithAnnotationClass.class, EnigmaBootstrapper.class);
|
||||||
|
}
|
||||||
|
|
||||||
private void assertBootstrapper(Class<?> testClass, Class<?> expectedBootstrapper) {
|
private void assertBootstrapper(Class<?> testClass, Class<?> expectedBootstrapper) {
|
||||||
BootstrapContext bootstrapContext = BootstrapTestUtils.buildBootstrapContext(testClass, delegate);
|
BootstrapContext bootstrapContext = BootstrapTestUtils.buildBootstrapContext(testClass, delegate);
|
||||||
TestContextBootstrapper bootstrapper = resolveTestContextBootstrapper(bootstrapContext);
|
TestContextBootstrapper bootstrapper = resolveTestContextBootstrapper(bootstrapContext);
|
||||||
@@ -112,6 +120,8 @@ public class BootstrapUtilsTests {
|
|||||||
|
|
||||||
static class BarBootstrapper extends DefaultTestContextBootstrapper {}
|
static class BarBootstrapper extends DefaultTestContextBootstrapper {}
|
||||||
|
|
||||||
|
static class EnigmaBootstrapper extends DefaultTestContextBootstrapper {}
|
||||||
|
|
||||||
@BootstrapWith(FooBootstrapper.class)
|
@BootstrapWith(FooBootstrapper.class)
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@interface BootWithFoo {}
|
@interface BootWithFoo {}
|
||||||
@@ -146,7 +156,12 @@ public class BootstrapUtilsTests {
|
|||||||
@BootWithFoo
|
@BootWithFoo
|
||||||
@BootWithFooAgain
|
@BootWithFooAgain
|
||||||
static class DuplicateMetaAnnotatedBootstrapWithAnnotationClass {}
|
static class DuplicateMetaAnnotatedBootstrapWithAnnotationClass {}
|
||||||
|
|
||||||
|
@BootWithFoo
|
||||||
|
@BootWithBar
|
||||||
|
@BootstrapWith(EnigmaBootstrapper.class)
|
||||||
|
static class LocalDeclarationAndMetaAnnotatedBootstrapWithAnnotationClass {}
|
||||||
|
|
||||||
@WebAppConfiguration
|
@WebAppConfiguration
|
||||||
static class WebAppConfigurationAnnotatedClass {}
|
static class WebAppConfigurationAnnotatedClass {}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user