diff --git a/spring-test/src/main/java/org/springframework/test/context/TestConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/TestConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..9be0f481875c7d88f25c9b7af64cfc66ddb6881d --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/TestConfiguration.java @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * {@code @TestConfiguration} is a type-level annotation that is used to configure + * how Spring test configuration annotations are processed within enclosing + * class hierarchies (i.e., for inner test classes). + * + *

If {@code @TestConfiguration} is not present or meta-present + * on a test class, configuration from the test class will not propagate to + * inner test classes (see {@value EnclosingConfiguration#OVERRIDE}). Consequently, + * inner test classes will have to declare their own Spring test configuration + * annotations. If you wish for an inner test class to inherit configuration from + * its enclosing class, annotate the enclosing class with + * {@code @TestConfiguration(EnclosingConfiguration.INHERIT)}. + * + *

This annotation may be used as a meta-annotation to create custom + * composed annotations. + * + *

As of Spring Framework 5.2, the use of this annotation typically only makes + * sense in conjunction with {@link org.junit.jupiter.api.Nested @Nested} test + * classes in JUnit Jupiter. + * + * @author Sam Brannen + * @since 5.2 + * @see EnclosingConfiguration#INHERIT + * @see EnclosingConfiguration#OVERRIDE + * @see ContextConfiguration @ContextConfiguration + * @see ContextHierarchy @ContextHierarchy + * @see ActiveProfiles @ActiveProfiles + * @see TestPropertySource @TestPropertySource + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface TestConfiguration { + + /** + * Configures the {@link EnclosingConfiguration} mode. + */ + EnclosingConfiguration value(); + + + /** + * Enumeration of modes that dictate how test configuration from + * enclosing classes is processed for inner test classes. + */ + enum EnclosingConfiguration { + + /** + * Indicates that test configuration for an inner test class should be + * inherited from its {@linkplain Class#getEnclosingClass() + * enclosing class}, as if the enclosing class were a superclass. + */ + INHERIT, + + /** + * Indicates that test configuration for an inner test class should + * override configuration from its + * {@linkplain Class#getEnclosingClass() enclosing class}. + */ + OVERRIDE + + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java b/spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java index e5422ce85b8bd2a653bd074cc3193163d005711e..0776ce07fbf7c2107c7a50f8b54aa835fa6ac3d5 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java @@ -16,7 +16,6 @@ package org.springframework.test.context.support; -import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashMap; @@ -35,6 +34,8 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfigurationAttributes; import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.SmartContextLoader; +import org.springframework.test.context.TestConfiguration; +import org.springframework.test.context.TestConfiguration.EnclosingConfiguration; import org.springframework.test.util.MetaAnnotationUtils.UntypedAnnotationDescriptor; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -240,8 +241,7 @@ abstract class ContextLoaderUtils { Assert.notNull(testClass, "Class must not be null"); Class annotationType = ContextConfiguration.class; - MergedAnnotations mergedAnnotations = MergedAnnotations.from(testClass, - SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES); + MergedAnnotations mergedAnnotations = MergedAnnotations.from(testClass, getSearchStrategy(testClass)); Assert.isTrue(mergedAnnotations.isPresent(annotationType), () -> String.format( "Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]", annotationType.getName(), testClass.getName())); @@ -252,6 +252,18 @@ abstract class ContextLoaderUtils { return attributesList; } + private static SearchStrategy getSearchStrategy(Class testClass) { + EnclosingConfiguration enclosingConfiguration = + MergedAnnotations.from(testClass, SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES) + .stream(TestConfiguration.class) + .map(mergedAnnotation -> mergedAnnotation.getEnum("value", EnclosingConfiguration.class)) + .findFirst() + .orElse(EnclosingConfiguration.OVERRIDE); + return (enclosingConfiguration == EnclosingConfiguration.INHERIT ? + SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES : + SearchStrategy.TYPE_HIERARCHY); + } + private static void resolveContextConfigurationAttributes(List attributesList, MergedAnnotation mergedAnnotation) { diff --git a/spring-test/src/test/java/org/springframework/test/context/support/AbstractContextConfigurationUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/support/AbstractContextConfigurationUtilsTests.java index 7e3556cf488ff2cd40a0092fdedbca463c92dcfc..3c626b74c7176c9f74ad01782ca396ab9e9ffb80 100644 --- a/spring-test/src/test/java/org/springframework/test/context/support/AbstractContextConfigurationUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/AbstractContextConfigurationUtilsTests.java @@ -36,6 +36,8 @@ import org.springframework.test.context.ContextConfigurationAttributes; import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.ContextLoader; import org.springframework.test.context.MergedContextConfiguration; +import org.springframework.test.context.TestConfiguration; +import org.springframework.test.context.TestConfiguration.EnclosingConfiguration; import org.springframework.test.context.TestContextBootstrapper; import org.springframework.test.context.web.WebAppConfiguration; @@ -231,6 +233,7 @@ abstract class AbstractContextConfigurationUtilsTests { @ContextConfiguration(classes = FooConfig.class, loader = AnnotationConfigContextLoader.class) @ActiveProfiles("foo") + @TestConfiguration(EnclosingConfiguration.INHERIT) static class OuterTestCase { class NestedTestCaseWithInheritedConfig { @@ -252,6 +255,7 @@ abstract class AbstractContextConfigurationUtilsTests { @ContextConfiguration(classes = FooConfig.class, loader = AnnotationConfigContextLoader.class, name = "foo"), // @ContextConfiguration(classes = BarConfig.class, loader = AnnotationConfigContextLoader.class)// }) + @TestConfiguration(EnclosingConfiguration.INHERIT) static class ContextHierarchyOuterTestCase { class NestedTestCaseWithInheritedConfig {