Skip to content

Commit 690694d

Browse files
committed
Merge branch 'sbrannen/issues/gh-23320-repeatable-TestPropertySource'
2 parents 8574f97 + 1075bae commit 690694d

26 files changed

+943
-129
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
1919
import java.lang.annotation.Documented;
2020
import java.lang.annotation.ElementType;
2121
import java.lang.annotation.Inherited;
22+
import java.lang.annotation.Repeatable;
2223
import java.lang.annotation.Retention;
2324
import java.lang.annotation.RetentionPolicy;
2425
import java.lang.annotation.Target;
@@ -67,6 +68,8 @@
6768
* <ul>
6869
* <li>Typically, {@code @TestPropertySource} will be used in conjunction with
6970
* {@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>
7073
* <li>This annotation may be used as a <em>meta-annotation</em> to create
7174
* custom <em>composed annotations</em>; however, caution should be taken if
7275
* this annotation and {@code @ContextConfiguration} are combined on a composed
@@ -86,6 +89,7 @@
8689
@Retention(RetentionPolicy.RUNTIME)
8790
@Documented
8891
@Inherited
92+
@Repeatable(TestPropertySources.class)
8993
public @interface TestPropertySource {
9094

9195
/**
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2002-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.context;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Inherited;
22+
import java.lang.annotation.Retention;
23+
import java.lang.annotation.RetentionPolicy;
24+
import java.lang.annotation.Target;
25+
26+
/**
27+
* {@code @TestPropertySources} is a container for one or more
28+
* {@link TestPropertySource @TestPropertySource} declarations.
29+
*
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.
33+
*
34+
* @author Anatoliy Korovin
35+
* @author Sam Brannen
36+
* @since 5.2
37+
*/
38+
@Target(ElementType.TYPE)
39+
@Retention(RetentionPolicy.RUNTIME)
40+
@Documented
41+
@Inherited
42+
public @interface TestPropertySources {
43+
44+
/**
45+
* An array of one or more {@link TestPropertySource @TestPropertySource}
46+
* declarations.
47+
*/
48+
TestPropertySource[] value();
49+
50+
}

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)