diff --git a/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java b/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java index 6935c247d08..31961d8504f 100644 --- a/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java @@ -26,7 +26,6 @@ import org.springframework.beans.BeanUtils; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.lang.Nullable; -import org.springframework.util.Assert; import org.springframework.util.ClassUtils; /** @@ -154,10 +153,19 @@ abstract class BootstrapUtils { if (annotations.isEmpty()) { 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", testClass.getName(), annotations)); - return annotations.iterator().next().value(); } private static Class resolveDefaultTestContextBootstrapper(Class testClass) throws Exception { diff --git a/spring-test/src/test/java/org/springframework/test/context/BootstrapUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/BootstrapUtilsTests.java index 4fdee627aff..48488545169 100644 --- a/spring-test/src/test/java/org/springframework/test/context/BootstrapUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/BootstrapUtilsTests.java @@ -99,6 +99,14 @@ public class BootstrapUtilsTests { assertBootstrapper(DuplicateMetaAnnotatedBootstrapWithAnnotationClass.class, FooBootstrapper.class); } + /** + * @since 5.1 + */ + @Test + public void resolveTestContextBootstrapperWithLocalDeclarationThatOverridesMetaBootstrapWithAnnotations() { + assertBootstrapper(LocalDeclarationAndMetaAnnotatedBootstrapWithAnnotationClass.class, EnigmaBootstrapper.class); + } + private void assertBootstrapper(Class testClass, Class expectedBootstrapper) { BootstrapContext bootstrapContext = BootstrapTestUtils.buildBootstrapContext(testClass, delegate); TestContextBootstrapper bootstrapper = resolveTestContextBootstrapper(bootstrapContext); @@ -112,6 +120,8 @@ public class BootstrapUtilsTests { static class BarBootstrapper extends DefaultTestContextBootstrapper {} + static class EnigmaBootstrapper extends DefaultTestContextBootstrapper {} + @BootstrapWith(FooBootstrapper.class) @Retention(RetentionPolicy.RUNTIME) @interface BootWithFoo {} @@ -146,7 +156,12 @@ public class BootstrapUtilsTests { @BootWithFoo @BootWithFooAgain static class DuplicateMetaAnnotatedBootstrapWithAnnotationClass {} - + + @BootWithFoo + @BootWithBar + @BootstrapWith(EnigmaBootstrapper.class) + static class LocalDeclarationAndMetaAnnotatedBootstrapWithAnnotationClass {} + @WebAppConfiguration static class WebAppConfigurationAnnotatedClass {}