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
This commit is contained in:
Sam Brannen
2023-08-16 09:44:46 +02:00
parent 3a38bb48b5
commit 1f544f113a
7 changed files with 98 additions and 16 deletions

View File

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

View File

@@ -47,6 +47,8 @@ import org.springframework.core.io.support.DefaultPropertySourceFactory;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceDescriptor;
import org.springframework.core.io.support.PropertySourceFactory;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.lang.Nullable;
import org.springframework.test.context.TestContextAnnotationUtils;
import org.springframework.test.context.TestPropertySource;
@@ -202,6 +204,8 @@ public abstract class TestPropertySourceUtils {
* <p>Property placeholders in resource locations (i.e., <code>${...}</code>)
* will be {@linkplain Environment#resolveRequiredPlaceholders(String) resolved}
* against the {@code Environment}.
* <p>A {@link ResourcePatternResolver} will be used to resolve resource
* location patterns into multiple resource locations.
* <p>Each properties file will be converted to a
* {@link org.springframework.core.io.support.ResourcePropertySource ResourcePropertySource}
* that will be added to the {@link PropertySources} of the environment with
@@ -258,13 +262,15 @@ public abstract class TestPropertySourceUtils {
* <p>Property placeholders in resource locations (i.e., <code>${...}</code>)
* will be {@linkplain Environment#resolveRequiredPlaceholders(String) resolved}
* against the {@code Environment}.
* <p>A {@link ResourcePatternResolver} will be used to resolve resource
* location patterns into multiple resource locations.
* <p>Each {@link PropertySource} will be created via the configured
* {@link PropertySourceDescriptor#propertySourceFactory() PropertySourceFactory}
* (or the {@link DefaultPropertySourceFactory} if no factory is configured)
* and added to the {@link PropertySources} of the environment with the highest
* precedence.
* @param environment the environment to update; never {@code null}
* @param resourceLoader the {@code ResourceLoader} to use to load each resource;
* @param resourceLoader the {@code ResourceLoader} to use to load resources;
* never {@code null}
* @param descriptors the property source descriptors to process; potentially
* empty but never {@code null}
@@ -282,6 +288,8 @@ public abstract class TestPropertySourceUtils {
Assert.notNull(environment, "'environment' must not be null");
Assert.notNull(resourceLoader, "'resourceLoader' must not be null");
Assert.notNull(descriptors, "'descriptors' must not be null");
ResourcePatternResolver resourcePatternResolver =
ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
MutablePropertySources propertySources = environment.getPropertySources();
try {
for (PropertySourceDescriptor descriptor : descriptors) {
@@ -292,10 +300,11 @@ public abstract class TestPropertySourceUtils {
for (String location : descriptor.locations()) {
String resolvedLocation = environment.resolveRequiredPlaceholders(location);
Resource resource = resourceLoader.getResource(resolvedLocation);
PropertySource<?> propertySource = factory.createPropertySource(descriptor.name(),
new EncodedResource(resource, descriptor.encoding()));
propertySources.addFirst(propertySource);
for (Resource resource : resourcePatternResolver.getResources(resolvedLocation)) {
PropertySource<?> propertySource = factory.createPropertySource(descriptor.name(),
new EncodedResource(resource, descriptor.encoding()));
propertySources.addFirst(propertySource);
}
}
}
}