diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java index 7cc828dd088..4075e367d3a 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java @@ -250,7 +250,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) { List defaultConfigAttributesList = - Collections.singletonList(new ContextConfigurationAttributes(testClass)); + ContextLoaderUtils.resolveDefaultContextConfigurationAttributes(testClass); ContextLoader contextLoader = resolveContextLoader(testClass, defaultConfigAttributesList); if (logger.isTraceEnabled()) { 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 ab7b5165e0e..96527a38674 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 @@ -30,6 +30,7 @@ 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.TestContextAnnotationUtils; import org.springframework.test.context.TestContextAnnotationUtils.AnnotationDescriptor; import org.springframework.test.context.TestContextAnnotationUtils.UntypedAnnotationDescriptor; import org.springframework.util.Assert; @@ -92,6 +93,7 @@ abstract class ContextLoaderUtils { * {@code @ContextHierarchy} as top-level annotations. * @since 3.2.2 * @see #buildContextHierarchyMap(Class) + * @see #resolveDefaultContextConfigurationAttributes(Class) * @see #resolveContextConfigurationAttributes(Class) */ @SuppressWarnings("unchecked") @@ -215,6 +217,44 @@ abstract class ContextLoaderUtils { return map; } + /** + * Resolve the list of default {@linkplain ContextConfigurationAttributes + * context configuration attributes} for the supplied {@linkplain Class test class} + * and its superclasses and enclosing classes. + *

Use this method instead of {@link #resolveContextConfigurationAttributes(Class)} + * if neither {@link ContextConfiguration @ContextConfiguration} nor + * {@link ContextHierarchy @ContextHierarchy} is present in the class hierarchy + * of the supplied test class. + * @param testClass the class for which to resolve the configuration attributes + * (must not be {@code null}) + * @return the list of configuration attributes for the specified class, ordered + * bottom-up (i.e., as if we were traversing up the class hierarchy + * and enclosing class hierarchy); never {@code null} + * @throws IllegalArgumentException if the supplied class is {@code null} + * @since 7.0.2 + */ + static List resolveDefaultContextConfigurationAttributes(Class testClass) { + Assert.notNull(testClass, "Class must not be null"); + List results = new ArrayList<>(); + resolveDefaultContextConfigurationAttributes(results, testClass); + return results; + } + + private static void resolveDefaultContextConfigurationAttributes( + List results, Class testClass) { + + results.add(0, new ContextConfigurationAttributes(testClass)); + + Class superclass = testClass.getSuperclass(); + if (superclass != null && superclass != Object.class) { + resolveDefaultContextConfigurationAttributes(results, superclass); + } + + if (TestContextAnnotationUtils.searchEnclosingClass(testClass)) { + resolveDefaultContextConfigurationAttributes(results, testClass.getEnclosingClass()); + } + } + /** * Resolve the list of {@linkplain ContextConfigurationAttributes context * configuration attributes} for the supplied {@linkplain Class test class} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/DefaultContextConfigurationDetectionWithNestedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/DefaultContextConfigurationDetectionWithNestedTests.java new file mode 100644 index 00000000000..c1c81528e39 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/DefaultContextConfigurationDetectionWithNestedTests.java @@ -0,0 +1,74 @@ +/* + * Copyright 2002-present 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.junit.jupiter.nested; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for detection of default context configuration within + * nested test class hierarchies without the use of {@link ContextConfiguration}. + * + * @author Sam Brannen + * @since 7.0.2 + * @see gh-31456 + */ +@ExtendWith(SpringExtension.class) +class DefaultContextConfigurationDetectionWithNestedTests { + + @Autowired + String greeting; + + @Test + void test(@Autowired String localGreeting) { + // This class must NOT be annotated with @SpringJUnitConfig or @ContextConfiguration. + assertThat(AnnotatedElementUtils.hasAnnotation(getClass(), ContextConfiguration.class)).isFalse(); + + assertThat(greeting).isEqualTo("TEST"); + assertThat(localGreeting).isEqualTo("TEST"); + } + + @Nested + class NestedTests { + + @Test + void test(@Autowired String localGreeting) { + assertThat(greeting).isEqualTo("TEST"); + assertThat(localGreeting).isEqualTo("TEST"); + } + } + + @Configuration + static class DefaultConfig { + + @Bean + String greeting() { + return "TEST"; + } + } + +}