Skip to content

Commit 0af09e0

Browse files
committed
Make default @NestedTestConfiguration mode configurable
Prior to this commit, the EnclosingConfiguration mode used in conjunction with @NestedTestConfiguration defaulted to INHERIT. In other to allow development teams to change the default to OVERRIDE (e.g., for compatibility with Spring Framework 5.0 through 5.2), this commit introduces support for changing the default EnclosingConfiguration mode globally via a JVM system property or via the SpringProperties mechanism. For example, the default may be changed to EnclosingConfiguration.OVERRIDE by supplying the following JVM system property via the command line. -Dspring.test.enclosing.configuration=override Closes gh-19930
1 parent 7f36594 commit 0af09e0

File tree

3 files changed

+122
-5
lines changed

3 files changed

+122
-5
lines changed

spring-test/src/main/java/org/springframework/test/context/NestedTestConfiguration.java

+64-3
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,29 @@
2323
import java.lang.annotation.RetentionPolicy;
2424
import java.lang.annotation.Target;
2525

26+
import org.apache.commons.logging.Log;
27+
import org.apache.commons.logging.LogFactory;
28+
29+
import org.springframework.lang.Nullable;
30+
2631
/**
2732
* {@code @NestedTestConfiguration} is a type-level annotation that is used to
2833
* configure how Spring test configuration annotations are processed within
2934
* enclosing class hierarchies (i.e., for <em>inner</em> test classes).
3035
*
3136
* <p>If {@code @NestedTestConfiguration} is not <em>present</em> or
37+
* <em>meta-present</em> on a test class, in its super type hierarchy, or in its
38+
* enclosing class hierarchy, the default <em>enclosing configuration inheritance
39+
* mode</em> will be used. See {@link #ENCLOSING_CONFIGURATION_PROPERTY_NAME} for
40+
* details on how to change the default mode.
41+
*
42+
* <p>By default, if {@code @NestedTestConfiguration} is not <em>present</em> or
3243
* <em>meta-present</em> on a test class, configuration from the test class will
3344
* propagate to inner test classes (see {@link EnclosingConfiguration#INHERIT}).
3445
* If {@code @NestedTestConfiguration(OVERRIDE)} is used to switch the mode,
3546
* inner test classes will have to declare their own Spring test configuration
3647
* annotations. If you wish to explicitly configure the mode, annotate either
37-
* the inner test class or the enclosing class with
48+
* the inner test class or an enclosing class with
3849
* {@code @NestedTestConfiguration(...}. Note that a
3950
* {@code @NestedTestConfiguration(...)} declaration is inherited within the
4051
* superclass hierarchy as well as within the enclosing class hierarchy. Thus,
@@ -57,21 +68,44 @@
5768
* @see ActiveProfiles @ActiveProfiles
5869
* @see TestPropertySource @TestPropertySource
5970
*/
60-
@Target({ElementType.TYPE, ElementType.METHOD})
71+
@Target(ElementType.TYPE)
6172
@Retention(RetentionPolicy.RUNTIME)
6273
@Documented
6374
@Inherited
6475
public @interface NestedTestConfiguration {
6576

77+
/**
78+
* JVM system property used to change the default <em>enclosing configuration
79+
* inheritance mode</em>: {@value #ENCLOSING_CONFIGURATION_PROPERTY_NAME}.
80+
* <p>Supported values include enum constants defined in
81+
* {@link EnclosingConfiguration}, ignoring case. For example, the default
82+
* may be changed to {@link EnclosingConfiguration#OVERRIDE} by supplying
83+
* the following JVM system property via the command line.
84+
* <pre style="code">-Dspring.test.enclosing.configuration=override</pre>
85+
* <p>If the property is not set to {@code OVERRIDE}, test configuration for
86+
* an inner test class will be <em>inherited</em> according to
87+
* {@link EnclosingConfiguration#INHERIT} semantics by default.
88+
* <p>May alternatively be configured via the
89+
* {@link org.springframework.core.SpringProperties SpringProperties}
90+
* mechanism.
91+
* @see #value
92+
*/
93+
String ENCLOSING_CONFIGURATION_PROPERTY_NAME = "spring.test.enclosing.configuration";
94+
95+
6696
/**
6797
* Configures the {@link EnclosingConfiguration} mode.
98+
* @see EnclosingConfiguration#INHERIT
99+
* @see EnclosingConfiguration#OVERRIDE
68100
*/
69101
EnclosingConfiguration value();
70102

71103

72104
/**
73105
* Enumeration of <em>modes</em> that dictate how test configuration from
74106
* enclosing classes is processed for inner test classes.
107+
* @see #INHERIT
108+
* @see #OVERRIDE
75109
*/
76110
enum EnclosingConfiguration {
77111

@@ -87,7 +121,34 @@ enum EnclosingConfiguration {
87121
* <em>override</em> configuration from its
88122
* {@linkplain Class#getEnclosingClass() enclosing class}.
89123
*/
90-
OVERRIDE
124+
OVERRIDE;
125+
126+
127+
/**
128+
* Get the {@code EnclosingConfiguration} enum constant with the supplied
129+
* name, ignoring case.
130+
* @param name the name of the enum constant to retrieve
131+
* @return the corresponding enum constant or {@code null} if not found
132+
* @see EnclosingConfiguration#valueOf(String)
133+
*/
134+
@Nullable
135+
public static EnclosingConfiguration from(@Nullable String name) {
136+
if (name == null) {
137+
return null;
138+
}
139+
try {
140+
return EnclosingConfiguration.valueOf(name.trim().toUpperCase());
141+
}
142+
catch (IllegalArgumentException ex) {
143+
Log logger = LogFactory.getLog(EnclosingConfiguration.class);
144+
if (logger.isDebugEnabled()) {
145+
logger.debug(String.format(
146+
"Failed to parse enclosing configuration mode from '%s': %s",
147+
name, ex.getMessage()));
148+
}
149+
return null;
150+
}
151+
}
91152

92153
}
93154

spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java

+8-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.HashSet;
2121
import java.util.Set;
2222

23+
import org.springframework.core.SpringProperties;
2324
import org.springframework.core.annotation.AnnotatedElementUtils;
2425
import org.springframework.core.annotation.AnnotationAttributes;
2526
import org.springframework.core.annotation.AnnotationUtils;
@@ -318,12 +319,17 @@ private static EnclosingConfiguration getEnclosingConfiguration(Class<?> clazz)
318319
}
319320

320321
private static EnclosingConfiguration lookUpEnclosingConfiguration(Class<?> clazz) {
321-
// TODO Make the default EnclosingConfiguration mode globally configurable via SpringProperties.
322322
return MergedAnnotations.from(clazz, SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES)
323323
.stream(NestedTestConfiguration.class)
324324
.map(mergedAnnotation -> mergedAnnotation.getEnum("value", EnclosingConfiguration.class))
325325
.findFirst()
326-
.orElse(EnclosingConfiguration.INHERIT);
326+
.orElseGet(MetaAnnotationUtils::getDefaultEnclosingConfigurationMode);
327+
}
328+
329+
private static EnclosingConfiguration getDefaultEnclosingConfigurationMode() {
330+
String value = SpringProperties.getProperty(NestedTestConfiguration.ENCLOSING_CONFIGURATION_PROPERTY_NAME);
331+
EnclosingConfiguration enclosingConfigurationMode = EnclosingConfiguration.from(value);
332+
return (enclosingConfigurationMode != null ? enclosingConfigurationMode : EnclosingConfiguration.INHERIT);
327333
}
328334

329335
private static void assertNonEmptyAnnotationTypeArray(Class<?>[] annotationTypes, String message) {

spring-test/src/test/java/org/springframework/test/util/MetaAnnotationUtilsTests.java

+50
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,27 @@
2222
import java.lang.annotation.RetentionPolicy;
2323
import java.lang.annotation.Target;
2424

25+
import org.junit.jupiter.api.AfterEach;
2526
import org.junit.jupiter.api.DisplayName;
2627
import org.junit.jupiter.api.Nested;
2728
import org.junit.jupiter.api.Test;
2829

30+
import org.springframework.core.SpringProperties;
2931
import org.springframework.core.annotation.AnnotationUtils;
3032
import org.springframework.core.annotation.Order;
3133
import org.springframework.stereotype.Component;
3234
import org.springframework.stereotype.Service;
3335
import org.springframework.test.context.ContextConfiguration;
36+
import org.springframework.test.context.NestedTestConfiguration;
3437
import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor;
3538
import org.springframework.test.util.MetaAnnotationUtils.UntypedAnnotationDescriptor;
3639
import org.springframework.transaction.annotation.Transactional;
3740

3841
import static org.assertj.core.api.Assertions.assertThat;
42+
import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.OVERRIDE;
3943
import static org.springframework.test.util.MetaAnnotationUtils.findAnnotationDescriptor;
4044
import static org.springframework.test.util.MetaAnnotationUtils.findAnnotationDescriptorForTypes;
45+
import static org.springframework.test.util.MetaAnnotationUtils.searchEnclosingClass;
4146

4247
/**
4348
* Unit tests for {@link MetaAnnotationUtils}.
@@ -48,6 +53,35 @@
4853
*/
4954
class MetaAnnotationUtilsTests {
5055

56+
@Nested
57+
@DisplayName("searchEnclosingClass() tests")
58+
class SearchEnclosingClassTests {
59+
60+
@AfterEach
61+
void clearGlobalFlag() {
62+
setGlobalFlag(null);
63+
}
64+
65+
@Test
66+
void standardDefaultMode() {
67+
assertThat(searchEnclosingClass(OuterTestCase1.class)).isFalse();
68+
assertThat(searchEnclosingClass(OuterTestCase1.NestedTestCase.class)).isTrue();
69+
assertThat(searchEnclosingClass(OuterTestCase1.NestedTestCase.DoubleNestedTestCase.class)).isTrue();
70+
}
71+
72+
@Test
73+
void overriddenDefaultMode() {
74+
setGlobalFlag("\t" + OVERRIDE.name().toLowerCase() + " ");
75+
assertThat(searchEnclosingClass(OuterTestCase2.class)).isFalse();
76+
assertThat(searchEnclosingClass(OuterTestCase2.NestedTestCase.class)).isFalse();
77+
assertThat(searchEnclosingClass(OuterTestCase2.NestedTestCase.DoubleNestedTestCase.class)).isFalse();
78+
}
79+
80+
private void setGlobalFlag(String flag) {
81+
SpringProperties.setProperty(NestedTestConfiguration.ENCLOSING_CONFIGURATION_PROPERTY_NAME, flag);
82+
}
83+
}
84+
5185
@Nested
5286
@DisplayName("findAnnotationDescriptor() tests")
5387
class FindAnnotationDescriptorTests {
@@ -636,4 +670,20 @@ static class AnnotatedContextConfigClass {
636670
static class MetaAnnotatedAndSuperAnnotatedContextConfigClass extends AnnotatedContextConfigClass {
637671
}
638672

673+
// We need two variants of "OuterTestCase", since the results for searchEnclosingClass() get cached.
674+
static class OuterTestCase1 {
675+
class NestedTestCase {
676+
class DoubleNestedTestCase {
677+
}
678+
}
679+
}
680+
681+
// We need two variants of "OuterTestCase", since the results for searchEnclosingClass() get cached.
682+
static class OuterTestCase2 {
683+
class NestedTestCase {
684+
class DoubleNestedTestCase {
685+
}
686+
}
687+
}
688+
639689
}

0 commit comments

Comments
 (0)