Skip to content

Commit 136af0b

Browse files
committed
Overhaul repeatable @TestPropertySource support
Prior to this commit, if multiple, directly present `@TestPropertySource` annotations declared the same property, the precedence ordering was top-down instead of bottom-up, in contrast to the semantics for class hierarchies. In other words, a subsequent `@TestPropertySource` annotation could not override a property in a previous `@TestPropertySource` annotation. This commit overhauls the internals of `TestPropertySourceUtils` in order to provide proper support for property overrides within local, directly present `@TestPropertySource` declarations. Specifically, the `locations` and `properties` attributes from all `@TestPropertySource` declarations that are directly present or meta-present on a given class are now merged into a single instance of `TestPropertySourceAttributes` internally, with assertions in place to ensure that such "same level" `@TestPropertySource` declarations do not configure different values for the `inheritLocations` and `inheritProperties` flags. Effectively, all "same level" `@TestPropertySource` declarations are treated internally as if there were only one such annotation declared by the user. See spring-projectsgh-23320
1 parent 2e476ca commit 136af0b

File tree

36 files changed

+686
-853
lines changed

36 files changed

+686
-853
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@
6868
* <ul>
6969
* <li>Typically, {@code @TestPropertySource} will be used in conjunction with
7070
* {@link ContextConfiguration @ContextConfiguration}.</li>
71+
* <li>As of Spring Framework 5.2, {@code @TestPropertySource} can be used as a
72+
* <em>{@linkplain Repeatable repeatable}</em> annotation.</li>
7173
* <li>This annotation may be used as a <em>meta-annotation</em> to create
7274
* custom <em>composed annotations</em>; however, caution should be taken if
7375
* this annotation and {@code @ContextConfiguration} are combined on a composed

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

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,23 @@
1616

1717
package org.springframework.test.context;
1818

19-
2019
import java.lang.annotation.Documented;
2120
import java.lang.annotation.ElementType;
2221
import java.lang.annotation.Inherited;
2322
import java.lang.annotation.Retention;
2423
import java.lang.annotation.RetentionPolicy;
2524
import java.lang.annotation.Target;
2625

27-
2826
/**
29-
* {@code @TestPropertySources} is a container for one or more {@link TestPropertySource}
30-
* declarations.
27+
* {@code @TestPropertySources} is a container for one or more
28+
* {@link TestPropertySource @TestPropertySource} declarations.
3129
*
32-
* <p>Note, however, that use of the {@code @TestPropertySources} container is completely
33-
* optional since {@code @TestPropertySource} is a {@linkplain java.lang.annotation.Repeatable
34-
* repeatable} annotation.
30+
* <p>Note, however, that use of the {@code @TestPropertySources} container is
31+
* completely optional since {@code @TestPropertySource} is a
32+
* {@linkplain java.lang.annotation.Repeatable repeatable} annotation.
3533
*
3634
* @author Anatoliy Korovin
35+
* @author Sam Brannen
3736
* @since 5.2
3837
*/
3938
@Target(ElementType.TYPE)
@@ -43,9 +42,9 @@
4342
public @interface TestPropertySources {
4443

4544
/**
46-
* An array of one or more {@link TestPropertySource} declarations.
47-
*
48-
* @return array of {@link TestPropertySource} values.
45+
* An array of one or more {@link TestPropertySource @TestPropertySource}
46+
* declarations.
4947
*/
5048
TestPropertySource[] value();
49+
5150
}

spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceAttributes.java

Lines changed: 26 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,19 @@
1616

1717
package org.springframework.test.context.support;
1818

19-
import org.apache.commons.logging.Log;
20-
import org.apache.commons.logging.LogFactory;
19+
import java.util.List;
2120

22-
import org.springframework.core.io.ClassPathResource;
2321
import org.springframework.core.style.ToStringCreator;
24-
import org.springframework.lang.Nullable;
2522
import org.springframework.test.context.TestPropertySource;
2623
import org.springframework.util.Assert;
27-
import org.springframework.util.ClassUtils;
2824
import org.springframework.util.ObjectUtils;
29-
import org.springframework.util.ResourceUtils;
3025

3126
/**
32-
* {@code TestPropertySourceAttributes} encapsulates the attributes declared
33-
* via {@link TestPropertySource @TestPropertySource}.
27+
* {@code TestPropertySourceAttributes} encapsulates attributes declared
28+
* via {@link TestPropertySource @TestPropertySource} annotations.
3429
*
3530
* <p>In addition to encapsulating declared attributes,
36-
* {@code TestPropertySourceAttributes} also enforces configuration rules
37-
* and detects default properties files.
31+
* {@code TestPropertySourceAttributes} also enforces configuration rules.
3832
*
3933
* @author Sam Brannen
4034
* @since 4.1
@@ -43,8 +37,6 @@
4337
*/
4438
class TestPropertySourceAttributes {
4539

46-
private static final Log logger = LogFactory.getLog(TestPropertySourceAttributes.class);
47-
4840
private final Class<?> declaringClass;
4941

5042
private final String[] locations;
@@ -57,27 +49,29 @@ class TestPropertySourceAttributes {
5749

5850

5951
/**
60-
* Create a new {@code TestPropertySourceAttributes} instance for the
61-
* supplied {@link TestPropertySource @TestPropertySource} annotation and
62-
* the {@linkplain Class test class} that declared it, enforcing
63-
* configuration rules and detecting a default properties file if
64-
* necessary.
52+
* Create a new {@code TestPropertySourceAttributes} instance for the supplied
53+
* values and enforce configuration rules.
6554
* @param declaringClass the class that declared {@code @TestPropertySource}
66-
* @param testPropertySource the annotation from which to retrieve the attributes
67-
* @since 4.2
55+
* @param locations the merged {@link TestPropertySource#locations()}
56+
* @param inheritLocations the {@link TestPropertySource#inheritLocations()} flag
57+
* @param properties the merged {@link TestPropertySource#properties()}
58+
* @param inheritProperties the {@link TestPropertySource#inheritProperties()} flag
59+
* @since 5.2
6860
*/
69-
TestPropertySourceAttributes(Class<?> declaringClass, TestPropertySource testPropertySource) {
70-
this(declaringClass, testPropertySource.locations(), testPropertySource.inheritLocations(),
71-
testPropertySource.properties(), testPropertySource.inheritProperties());
61+
TestPropertySourceAttributes(Class<?> declaringClass, List<String> locations, boolean inheritLocations,
62+
List<String> properties, boolean inheritProperties) {
63+
64+
this(declaringClass, locations.toArray(new String[0]), inheritLocations, properties.toArray(new String[0]),
65+
inheritProperties);
7266
}
7367

7468
private TestPropertySourceAttributes(Class<?> declaringClass, String[] locations, boolean inheritLocations,
7569
String[] properties, boolean inheritProperties) {
7670

77-
Assert.notNull(declaringClass, "declaringClass must not be null");
78-
if (ObjectUtils.isEmpty(locations) && ObjectUtils.isEmpty(properties)) {
79-
locations = new String[] { detectDefaultPropertiesFile(declaringClass) };
80-
}
71+
Assert.notNull(declaringClass, "'declaringClass' must not be null");
72+
Assert.isTrue(!ObjectUtils.isEmpty(locations) || !ObjectUtils.isEmpty(properties),
73+
"Either 'locations' or 'properties' are required");
74+
8175
this.declaringClass = declaringClass;
8276
this.locations = locations;
8377
this.inheritLocations = inheritLocations;
@@ -97,7 +91,8 @@ Class<?> getDeclaringClass() {
9791
/**
9892
* Get the resource locations that were declared via {@code @TestPropertySource}.
9993
* <p>Note: The returned value may represent a <em>detected default</em>
100-
* that does not match the original value declared via {@code @TestPropertySource}.
94+
* or merged locations that do not match the original value declared via a
95+
* single {@code @TestPropertySource} annotation.
10196
* @return the resource locations; potentially <em>empty</em>
10297
* @see TestPropertySource#value
10398
* @see TestPropertySource#locations
@@ -117,10 +112,12 @@ boolean isInheritLocations() {
117112

118113
/**
119114
* Get the inlined properties that were declared via {@code @TestPropertySource}.
120-
* @return the inlined properties; potentially {@code null} or <em>empty</em>
115+
* <p>Note: The returned value may represent merged properties that do not
116+
* match the original value declared via a single {@code @TestPropertySource}
117+
* annotation.
118+
* @return the inlined properties; potentially <em>empty</em>
121119
* @see TestPropertySource#properties
122120
*/
123-
@Nullable
124121
String[] getProperties() {
125122
return this.properties;
126123
}
@@ -149,31 +146,4 @@ public String toString() {
149146
.toString();
150147
}
151148

152-
153-
/**
154-
* Detect a default properties file for the supplied class, as specified
155-
* in the class-level Javadoc for {@link TestPropertySource}.
156-
*/
157-
private static String detectDefaultPropertiesFile(Class<?> testClass) {
158-
String resourcePath = ClassUtils.convertClassNameToResourcePath(testClass.getName()) + ".properties";
159-
ClassPathResource classPathResource = new ClassPathResource(resourcePath);
160-
161-
if (classPathResource.exists()) {
162-
String prefixedResourcePath = ResourceUtils.CLASSPATH_URL_PREFIX + resourcePath;
163-
if (logger.isInfoEnabled()) {
164-
logger.info(String.format("Detected default properties file \"%s\" for test class [%s]",
165-
prefixedResourcePath, testClass.getName()));
166-
}
167-
return prefixedResourcePath;
168-
}
169-
else {
170-
String msg = String.format("Could not detect default properties file for test [%s]: " +
171-
"%s does not exist. Either declare the 'locations' or 'properties' attributes " +
172-
"of @TestPropertySource or make the default properties file available.", testClass.getName(),
173-
classPathResource);
174-
logger.error(msg);
175-
throw new IllegalStateException(msg);
176-
}
177-
}
178-
179149
}

0 commit comments

Comments
 (0)