Skip to content

Commit 1f544f1

Browse files
committed
Support resource patterns in @TestPropertySource locations
Inspired by the recently added support for resource patterns in @propertysource locations, this commit adds the same support for resource locations in @TestPropertySource. For example, assuming the `config` folder in the classpath contains only 3 files matching the pattern `file?.properties`, ... the following: @TestPropertySource("classpath:/config/file1.properties") @TestPropertySource("classpath:/config/file2.properties") @TestPropertySource("classpath:/config/file3.properties") ... or: @TestPropertySource({ "classpath:/config/file1.properties", "classpath:/config/file2.properties", "classpath:/config/file3.properties" }) ... can now be replaced by: @TestPropertySource("classpath*:/config/file?.properties") See gh-21325 Closes gh-31055
1 parent 3a38bb4 commit 1f544f1

File tree

7 files changed

+98
-16
lines changed

7 files changed

+98
-16
lines changed

framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/property-sources.adoc

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,12 @@ Each path is interpreted as a Spring `Resource`. A plain path (for example,
3737
in which the test class is defined. A path starting with a slash is treated as an
3838
absolute classpath resource (for example: `"/org/example/test.xml"`). A path that
3939
references a URL (for example, a path prefixed with `classpath:`, `file:`, or `http:`) is
40-
loaded by using the specified resource protocol. Resource location wildcards (such as
41-
`{asterisk}{asterisk}/{asterisk}.properties`) are not permitted: Each location must
42-
evaluate to exactly one properties resource.
40+
loaded by using the specified resource protocol.
41+
42+
Property placeholders in paths (such as `${...}`) will be resolved against the `Environment`.
43+
44+
As of Spring Framework 6.1, resource location patterns are also supported — for
45+
example, `"classpath*:/config/*.properties"`.
4346

4447
The following example uses a test properties file:
4548

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

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,10 @@
110110

111111
/**
112112
* The resource locations of properties files to be loaded into the
113-
* {@code Environment}'s set of {@code PropertySources}. Each location
114-
* will be added to the enclosing {@code Environment} as its own property
115-
* source, in the order declared.
113+
* {@code Environment}'s set of {@code PropertySources}.
114+
* <p>Each location will be added to the enclosing {@code Environment} as its
115+
* own property source, in the order declared (or in the order in which resource
116+
* locations are resolved when location wildcards are used).
116117
* <h4>Supported File Formats</h4>
117118
* <p>By default, both traditional and XML-based properties file formats are
118119
* supported &mdash; for example, {@code "classpath:/com/example/test.properties"}
@@ -130,11 +131,19 @@
130131
* {@link org.springframework.util.ResourceUtils#CLASSPATH_URL_PREFIX classpath:},
131132
* {@link org.springframework.util.ResourceUtils#FILE_URL_PREFIX file:},
132133
* {@code http:}, etc.) will be loaded using the specified resource protocol.
133-
* Resource location wildcards (e.g. <code>*&#42;/*.properties</code>)
134-
* are not permitted: each location must evaluate to exactly one properties
135-
* resource. Property placeholders in paths (i.e., <code>${...}</code>) will be
136-
* {@linkplain org.springframework.core.env.Environment#resolveRequiredPlaceholders(String) resolved}
137-
* against the {@code Environment}.
134+
* <p>Property placeholders in paths (i.e., <code>${...}</code>) will be
135+
* {@linkplain org.springframework.core.env.Environment#resolveRequiredPlaceholders(String)
136+
* resolved} against the {@code Environment}.
137+
* <p>As of Spring Framework 6.1, resource location patterns are also
138+
* supported &mdash; for example, {@code "classpath*:/config/*.properties"}.
139+
* <p><strong>WARNING</strong>: a pattern such as {@code "classpath*:/config/*.properties"}
140+
* may be effectively equivalent to an explicit enumeration of resource locations such as
141+
* <code>{"classpath:/config/mail.properties", classpath:/config/order.properties"}</code>;
142+
* however, the two declarations will result in different keys for the context
143+
* cache since the pattern cannot be eagerly resolved to concrete locations.
144+
* Consequently, to benefit from the context cache you must ensure that you
145+
* consistently use either patterns or explicit enumerations of resource
146+
* locations within your test suite.
138147
* <h4>Default Properties File Detection</h4>
139148
* <p>See the class-level Javadoc for a discussion on detection of defaults.
140149
* <h4>Precedence</h4>

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

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
import org.springframework.core.io.support.EncodedResource;
4848
import org.springframework.core.io.support.PropertySourceDescriptor;
4949
import org.springframework.core.io.support.PropertySourceFactory;
50+
import org.springframework.core.io.support.ResourcePatternResolver;
51+
import org.springframework.core.io.support.ResourcePatternUtils;
5052
import org.springframework.lang.Nullable;
5153
import org.springframework.test.context.TestContextAnnotationUtils;
5254
import org.springframework.test.context.TestPropertySource;
@@ -202,6 +204,8 @@ public static void addPropertiesFilesToEnvironment(ConfigurableApplicationContex
202204
* <p>Property placeholders in resource locations (i.e., <code>${...}</code>)
203205
* will be {@linkplain Environment#resolveRequiredPlaceholders(String) resolved}
204206
* against the {@code Environment}.
207+
* <p>A {@link ResourcePatternResolver} will be used to resolve resource
208+
* location patterns into multiple resource locations.
205209
* <p>Each properties file will be converted to a
206210
* {@link org.springframework.core.io.support.ResourcePropertySource ResourcePropertySource}
207211
* that will be added to the {@link PropertySources} of the environment with
@@ -258,13 +262,15 @@ public static void addPropertySourcesToEnvironment(ConfigurableApplicationContex
258262
* <p>Property placeholders in resource locations (i.e., <code>${...}</code>)
259263
* will be {@linkplain Environment#resolveRequiredPlaceholders(String) resolved}
260264
* against the {@code Environment}.
265+
* <p>A {@link ResourcePatternResolver} will be used to resolve resource
266+
* location patterns into multiple resource locations.
261267
* <p>Each {@link PropertySource} will be created via the configured
262268
* {@link PropertySourceDescriptor#propertySourceFactory() PropertySourceFactory}
263269
* (or the {@link DefaultPropertySourceFactory} if no factory is configured)
264270
* and added to the {@link PropertySources} of the environment with the highest
265271
* precedence.
266272
* @param environment the environment to update; never {@code null}
267-
* @param resourceLoader the {@code ResourceLoader} to use to load each resource;
273+
* @param resourceLoader the {@code ResourceLoader} to use to load resources;
268274
* never {@code null}
269275
* @param descriptors the property source descriptors to process; potentially
270276
* empty but never {@code null}
@@ -282,6 +288,8 @@ public static void addPropertySourcesToEnvironment(ConfigurableEnvironment envir
282288
Assert.notNull(environment, "'environment' must not be null");
283289
Assert.notNull(resourceLoader, "'resourceLoader' must not be null");
284290
Assert.notNull(descriptors, "'descriptors' must not be null");
291+
ResourcePatternResolver resourcePatternResolver =
292+
ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
285293
MutablePropertySources propertySources = environment.getPropertySources();
286294
try {
287295
for (PropertySourceDescriptor descriptor : descriptors) {
@@ -292,10 +300,11 @@ public static void addPropertySourcesToEnvironment(ConfigurableEnvironment envir
292300

293301
for (String location : descriptor.locations()) {
294302
String resolvedLocation = environment.resolveRequiredPlaceholders(location);
295-
Resource resource = resourceLoader.getResource(resolvedLocation);
296-
PropertySource<?> propertySource = factory.createPropertySource(descriptor.name(),
297-
new EncodedResource(resource, descriptor.encoding()));
298-
propertySources.addFirst(propertySource);
303+
for (Resource resource : resourcePatternResolver.getResources(resolvedLocation)) {
304+
PropertySource<?> propertySource = factory.createPropertySource(descriptor.name(),
305+
new EncodedResource(resource, descriptor.encoding()));
306+
propertySources.addFirst(propertySource);
307+
}
299308
}
300309
}
301310
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2002-2023 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.env;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import org.springframework.beans.factory.annotation.Autowired;
22+
import org.springframework.context.annotation.Configuration;
23+
import org.springframework.core.env.Environment;
24+
import org.springframework.test.context.TestPropertySource;
25+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
26+
27+
import static org.assertj.core.api.Assertions.assertThat;
28+
29+
/**
30+
* Integration tests for {@link TestPropertySource @TestPropertySource} support
31+
* for resource patterns for resource locations.
32+
*
33+
* @author Sam Brannen
34+
* @since 6.1
35+
*/
36+
@SpringJUnitConfig
37+
@TestPropertySource("classpath*:/org/springframework/test/context/env/pattern/file?.properties")
38+
class ResourcePatternExplicitPropertiesFileTests {
39+
40+
@Test
41+
void verifyPropertiesAreAvailableInEnvironment(@Autowired Environment env) {
42+
assertEnvironmentContainsProperties(env, "from.p1", "from.p2", "from.p3");
43+
}
44+
45+
46+
private static void assertEnvironmentContainsProperties(Environment env, String... names) {
47+
for (String name : names) {
48+
assertThat(env.containsProperty(name)).as("environment contains property '%s'", name).isTrue();
49+
}
50+
}
51+
52+
53+
@Configuration
54+
static class Config {
55+
/* no user beans required for these tests */
56+
}
57+
58+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from.p1 = 1
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from.p2 = 2
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from.p3 = 3

0 commit comments

Comments
 (0)