Switch the default @NestedTestConfiguration mode to INHERIT.

See gh-19930
This commit is contained in:
Sam Brannen 2020-10-12 18:33:20 +02:00
parent 8c47c7c797
commit fbb3c5cce7
12 changed files with 39 additions and 32 deletions

View File

@ -30,13 +30,13 @@ import java.lang.annotation.Target;
* *
* <p>If {@code @NestedTestConfiguration} is not <em>present</em> or * <p>If {@code @NestedTestConfiguration} is not <em>present</em> or
* <em>meta-present</em> on a test class, configuration from the test class will * <em>meta-present</em> on a test class, configuration from the test class will
* not propagate to inner test classes (see {@link EnclosingConfiguration#OVERRIDE}). * propagate to inner test classes (see {@link EnclosingConfiguration#INHERIT}).
* Consequently, inner test classes will have to declare their own Spring test * If {@code @NestedTestConfiguration(OVERRIDE)} is used to switch the mode,
* configuration annotations. If you wish for an inner test class to inherit * inner test classes will have to declare their own Spring test configuration
* configuration from its enclosing class, annotate either the inner test class * annotations. If you wish to explicitly configure the mode, annotate either
* or the enclosing class with * the inner test class or the enclosing class with
* {@code @NestedTestConfiguration(EnclosingConfiguration.INHERIT)}. Note that * {@code @NestedTestConfiguration(...}. Note that a
* a {@code @NestedTestConfiguration(...)} declaration is inherited within the * {@code @NestedTestConfiguration(...)} declaration is inherited within the
* superclass hierarchy as well as within the enclosing class hierarchy. Thus, * superclass hierarchy as well as within the enclosing class hierarchy. Thus,
* there is no need to redeclare the annotation unless you wish to switch the * there is no need to redeclare the annotation unless you wish to switch the
* mode. * mode.

View File

@ -68,8 +68,8 @@ import org.springframework.util.ObjectUtils;
*/ */
public abstract class MetaAnnotationUtils { public abstract class MetaAnnotationUtils {
private static final ConcurrentLruCache<Class<?>, SearchStrategy> cachedSearchStrategies = private static final ConcurrentLruCache<Class<?>, EnclosingConfiguration> cachedSearchStrategies =
new ConcurrentLruCache<>(32, MetaAnnotationUtils::lookUpSearchStrategy); new ConcurrentLruCache<>(32, MetaAnnotationUtils::lookUpEnclosingConfiguration);
/** /**
@ -301,35 +301,29 @@ public abstract class MetaAnnotationUtils {
* class should be searched * class should be searched
* @since 5.3 * @since 5.3
* @see ClassUtils#isInnerClass(Class) * @see ClassUtils#isInnerClass(Class)
* @see #getSearchStrategy(Class)
*/ */
public static boolean searchEnclosingClass(Class<?> clazz) { public static boolean searchEnclosingClass(Class<?> clazz) {
return (ClassUtils.isInnerClass(clazz) && return (ClassUtils.isInnerClass(clazz) &&
getSearchStrategy(clazz) == SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES); getEnclosingConfiguration(clazz) == EnclosingConfiguration.INHERIT);
} }
/** /**
* Get the {@link SearchStrategy} for the supplied class. * Get the {@link EnclosingConfiguration} mode for the supplied class.
* @param clazz the class for which the search strategy should be resolved * @param clazz the class for which the search strategy should be resolved
* @return the resolved search strategy * @return the resolved search strategy
* @since 5.3 * @since 5.3
*/ */
private static SearchStrategy getSearchStrategy(Class<?> clazz) { private static EnclosingConfiguration getEnclosingConfiguration(Class<?> clazz) {
return cachedSearchStrategies.get(clazz); return cachedSearchStrategies.get(clazz);
} }
private static SearchStrategy lookUpSearchStrategy(Class<?> clazz) { private static EnclosingConfiguration lookUpEnclosingConfiguration(Class<?> clazz) {
EnclosingConfiguration enclosingConfiguration = // TODO Make the default EnclosingConfiguration mode globally configurable via SpringProperties.
MergedAnnotations.from(clazz, SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES) return MergedAnnotations.from(clazz, SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES)
.stream(NestedTestConfiguration.class) .stream(NestedTestConfiguration.class)
.map(mergedAnnotation -> mergedAnnotation.getEnum("value", EnclosingConfiguration.class)) .map(mergedAnnotation -> mergedAnnotation.getEnum("value", EnclosingConfiguration.class))
.findFirst() .findFirst()
.orElse(EnclosingConfiguration.OVERRIDE); .orElse(EnclosingConfiguration.INHERIT);
// TODO Switch the default EnclosingConfiguration mode to INHERIT.
// TODO Make the default EnclosingConfiguration mode globally configurable via SpringProperties.
return (enclosingConfiguration == EnclosingConfiguration.INHERIT ?
SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES :
SearchStrategy.TYPE_HIERARCHY);
} }
private static void assertNonEmptyAnnotationTypeArray(Class<?>[] annotationTypes, String message) { private static void assertNonEmptyAnnotationTypeArray(Class<?>[] annotationTypes, String message) {
@ -505,10 +499,12 @@ public abstract class MetaAnnotationUtils {
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Set<T> findAllLocalMergedAnnotations() { public Set<T> findAllLocalMergedAnnotations() {
Class<T> annotationType = (Class<T>) getAnnotationType(); SearchStrategy searchStrategy =
SearchStrategy searchStrategy = getSearchStrategy(getRootDeclaringClass()); (getEnclosingConfiguration(getRootDeclaringClass()) == EnclosingConfiguration.INHERIT ?
SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES :
SearchStrategy.TYPE_HIERARCHY);
return MergedAnnotations.from(getRootDeclaringClass(), searchStrategy, RepeatableContainers.none()) return MergedAnnotations.from(getRootDeclaringClass(), searchStrategy, RepeatableContainers.none())
.stream(annotationType) .stream((Class<T>) getAnnotationType())
.filter(MergedAnnotationPredicates.firstRunOf(MergedAnnotation::getAggregateIndex)) .filter(MergedAnnotationPredicates.firstRunOf(MergedAnnotation::getAggregateIndex))
.collect(MergedAnnotationCollectors.toAnnotationSet()); .collect(MergedAnnotationCollectors.toAnnotationSet());
} }

View File

@ -46,6 +46,7 @@ import static org.springframework.test.context.NestedTestConfiguration.Enclosing
*/ */
@SpringJUnitConfig(Config1.class) @SpringJUnitConfig(Config1.class)
@ActiveProfiles("1") @ActiveProfiles("1")
@NestedTestConfiguration(OVERRIDE) // since INHERIT is now the global default
class ActiveProfilesNestedTests { class ActiveProfilesNestedTests {
@Autowired @Autowired

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2019 the original author or authors. * Copyright 2002-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -25,12 +25,14 @@ import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.NestedTestConfiguration;
import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite; import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite;
import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.test.context.junit.jupiter.nested.ConstructorInjectionNestedTests.TopLevelConfig; import org.springframework.test.context.junit.jupiter.nested.ConstructorInjectionNestedTests.TopLevelConfig;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.OVERRIDE;
/** /**
* Integration tests that verify support for {@code @Nested} test classes in conjunction * Integration tests that verify support for {@code @Nested} test classes in conjunction
@ -47,6 +49,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @see org.springframework.test.context.junit4.nested.NestedTestsWithSpringRulesTests * @see org.springframework.test.context.junit4.nested.NestedTestsWithSpringRulesTests
*/ */
@SpringJUnitConfig(TopLevelConfig.class) @SpringJUnitConfig(TopLevelConfig.class)
@NestedTestConfiguration(OVERRIDE) // since INHERIT is now the global default
class ConstructorInjectionNestedTests { class ConstructorInjectionNestedTests {
final String foo; final String foo;

View File

@ -44,6 +44,7 @@ import static org.springframework.test.context.NestedTestConfiguration.Enclosing
* @see org.springframework.test.context.junit4.nested.NestedTestsWithSpringRulesTests * @see org.springframework.test.context.junit4.nested.NestedTestsWithSpringRulesTests
*/ */
@SpringJUnitConfig(TopLevelConfig.class) @SpringJUnitConfig(TopLevelConfig.class)
@NestedTestConfiguration(OVERRIDE) // since INHERIT is now the global default
class ContextConfigurationNestedTests { class ContextConfigurationNestedTests {
private static final String FOO = "foo"; private static final String FOO = "foo";

View File

@ -45,6 +45,7 @@ import static org.springframework.test.context.NestedTestConfiguration.Enclosing
*/ */
@ExtendWith(SpringExtension.class) @ExtendWith(SpringExtension.class)
@ContextHierarchy(@ContextConfiguration(classes = ParentConfig.class)) @ContextHierarchy(@ContextConfiguration(classes = ParentConfig.class))
@NestedTestConfiguration(OVERRIDE) // since INHERIT is now the global default
class ContextHierarchyNestedTests { class ContextHierarchyNestedTests {
private static final String FOO = "foo"; private static final String FOO = "foo";

View File

@ -67,12 +67,6 @@ class SqlScriptNestedTests {
} }
@Nested @Nested
// NOTE: the following @SpringJUnitConfig declaration must NOT be removed.
// This was added before the TestContext framework looked up configuration
// on enclosing classes for @Nested test classes. As such, this serves as a
// regression test and cannot be changed.
@SpringJUnitConfig(PopulatedSchemaDatabaseConfig.class)
@Transactional
class NestedTests { class NestedTests {
@Autowired @Autowired

View File

@ -43,6 +43,7 @@ import static org.springframework.test.context.TestConstructor.AutowireMode.ANNO
*/ */
@SpringJUnitConfig @SpringJUnitConfig
@TestConstructor(autowireMode = ALL) @TestConstructor(autowireMode = ALL)
@NestedTestConfiguration(OVERRIDE) // since INHERIT is now the global default
class TestConstructorNestedTests { class TestConstructorNestedTests {
TestConstructorNestedTests(String text) { TestConstructorNestedTests(String text) {

View File

@ -46,6 +46,7 @@ import static org.springframework.test.context.NestedTestConfiguration.Enclosing
*/ */
@SpringJUnitConfig @SpringJUnitConfig
@TestExecutionListeners(FooTestExecutionListener.class) @TestExecutionListeners(FooTestExecutionListener.class)
@NestedTestConfiguration(OVERRIDE) // since INHERIT is now the global default
class TestExecutionListenersNestedTests { class TestExecutionListenersNestedTests {
private static final String FOO = "foo"; private static final String FOO = "foo";

View File

@ -42,6 +42,7 @@ import static org.springframework.test.context.NestedTestConfiguration.Enclosing
*/ */
@SpringJUnitConfig(Config.class) @SpringJUnitConfig(Config.class)
@TestPropertySource(properties = "p1 = v1") @TestPropertySource(properties = "p1 = v1")
@NestedTestConfiguration(OVERRIDE) // since INHERIT is now the global default
class TestPropertySourceNestedTests { class TestPropertySourceNestedTests {
@Autowired @Autowired
@ -160,6 +161,12 @@ class TestPropertySourceNestedTests {
} }
@Nested @Nested
// The following explicit INHERIT is necessary since this nested
// test class implements an interface whose enclosing class is
// annotated with @NestedTestConfiguration(OVERRIDE). In other
// words, the local declaration overrides the declaration
// "inherited" via the interface.
@NestedTestConfiguration(INHERIT)
class L5WithInheritedConfigAndTestInterfaceTests implements TestInterface { class L5WithInheritedConfigAndTestInterfaceTests implements TestInterface {
@Autowired @Autowired

View File

@ -53,6 +53,7 @@ import static org.springframework.transaction.annotation.Propagation.NOT_SUPPORT
@SpringJUnitConfig @SpringJUnitConfig
@Transactional @Transactional
@Commit @Commit
@NestedTestConfiguration(OVERRIDE) // since INHERIT is now the global default
class TransactionalNestedTests { class TransactionalNestedTests {
@Test @Test

View File

@ -44,6 +44,7 @@ import static org.springframework.test.context.NestedTestConfiguration.Enclosing
* @see org.springframework.test.context.junit4.nested.NestedTestsWithSpringRulesTests * @see org.springframework.test.context.junit4.nested.NestedTestsWithSpringRulesTests
*/ */
@SpringJUnitWebConfig(Config.class) @SpringJUnitWebConfig(Config.class)
@NestedTestConfiguration(OVERRIDE) // since INHERIT is now the global default
class WebAppConfigurationNestedTests { class WebAppConfigurationNestedTests {
@Test @Test