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 a63289371c0..f705ddad545 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 @@ -25,6 +25,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.core.annotation.AliasFor; import org.springframework.test.context.BootstrapUtilsTests.OuterClass.NestedWithInheritedBootstrapper; import org.springframework.test.context.BootstrapUtilsTests.OuterClass.NestedWithInheritedBootstrapper.DoubleNestedWithInheritedButOverriddenBootstrapper; import org.springframework.test.context.BootstrapUtilsTests.OuterClass.NestedWithInheritedBootstrapper.DoubleNestedWithOverriddenBootstrapper; @@ -140,6 +141,37 @@ class BootstrapUtilsTests { assertBootstrapper(LocalDeclarationAndMetaAnnotatedBootstrapWithAnnotationClass.class, EnigmaBootstrapper.class); } + /** + * @since 7.0.6 + */ + @Test // gh-35938 + void resolveTestContextBootstrapperWithMetaBootstrapWithAnnotationThatOverridesMetaMetaBootstrapWithAnnotation() { + BootstrapContext bootstrapContext = BootstrapTestUtils.buildBootstrapContext( + MetaAndMetaMetaBootstrapWithAnnotationsClass.class, delegate); + assertThatIllegalStateException() + .isThrownBy(() -> resolveTestContextBootstrapper(bootstrapContext)) + .withMessageContaining("Configuration error: found multiple declarations of @BootstrapWith") + .withMessageContaining(FooBootstrapper.class.getSimpleName()) + .withMessageContaining(BarBootstrapper.class.getSimpleName()); + } + + /** + * @since 7.0.6 + */ + @Test // gh-35938 + void resolveTestContextBootstrapperWithComposedBootstrapWithAnnotationThatOverridesMetaMetaBootstrapWithAnnotation() { + assertBootstrapper(ConfigurableAndMetaMetaBootstrapWithAnnotationsClass.class, BarBootstrapper.class); + } + + /** + * @since 7.0.6 + */ + @Test // gh-35938 + void resolveTestContextBootstrapperWithCustomizedComposedBootstrapWithAnnotationThatOverridesMetaMetaBootstrapWithAnnotation() { + assertBootstrapper(CustomizedConfigurableAndMetaMetaBootstrapWithAnnotationsClass.class, EnigmaBootstrapper.class); + } + + private void assertBootstrapper(Class> testClass, Class> expectedBootstrapper) { BootstrapContext bootstrapContext = BootstrapTestUtils.buildBootstrapContext(testClass, delegate); TestContextBootstrapper bootstrapper = resolveTestContextBootstrapper(bootstrapContext); @@ -167,6 +199,28 @@ class BootstrapUtilsTests { @Retention(RetentionPolicy.RUNTIME) @interface BootWithBar {} + /** + * This annotation emulates the expectation of certain Spring Boot users who wish + * to be able to override the {@code @BootstrapWith} meta-annotation + * declaration on {@code @SpringBootTest} while retaining the other configuration of + * {@code @SpringBootTest}. + *
In this annotation, {@code @BootWithFoo} plays the role of {@code @SpringBootTest}, + * which the user wishes to override via a local declaration of + * {@code @BootstrapWith(BarBootstrapper.class)}. + */ + @BootWithFoo + @BootstrapWith(BarBootstrapper.class) + @Retention(RetentionPolicy.RUNTIME) + @interface BootWithBarInsteadOfFoo {} + + @BootWithFoo + @Retention(RetentionPolicy.RUNTIME) + @interface BootWithConfigurableBootstrapperInsteadOfFoo { + + @AliasFor(annotation = BootstrapWith.class) + Class extends TestContextBootstrapper> value() default BarBootstrapper.class; + } + // Invalid @BootstrapWith static class EmptyBootstrapWithAnnotationClass {} @@ -195,6 +249,15 @@ class BootstrapUtilsTests { @BootstrapWith(EnigmaBootstrapper.class) static class LocalDeclarationAndMetaAnnotatedBootstrapWithAnnotationClass {} + @BootWithBarInsteadOfFoo + static class MetaAndMetaMetaBootstrapWithAnnotationsClass {} + + @BootWithConfigurableBootstrapperInsteadOfFoo + static class ConfigurableAndMetaMetaBootstrapWithAnnotationsClass {} + + @BootWithConfigurableBootstrapperInsteadOfFoo(EnigmaBootstrapper.class) + static class CustomizedConfigurableAndMetaMetaBootstrapWithAnnotationsClass {} + @org.springframework.test.context.web.WebAppConfiguration static class WebAppConfigClass {