diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/property-sources.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/property-sources.adoc index 63243f041d1e..ba6b365ea5f9 100644 --- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/property-sources.adoc +++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/property-sources.adoc @@ -37,9 +37,12 @@ Each path is interpreted as a Spring `Resource`. A plain path (for example, in which the test class is defined. A path starting with a slash is treated as an absolute classpath resource (for example: `"/org/example/test.xml"`). A path that references a URL (for example, a path prefixed with `classpath:`, `file:`, or `http:`) is -loaded by using the specified resource protocol. Resource location wildcards (such as -`{asterisk}{asterisk}/{asterisk}.properties`) are not permitted: Each location must -evaluate to exactly one properties resource. +loaded by using the specified resource protocol. + +Property placeholders in paths (such as `${...}`) will be resolved against the `Environment`. + +As of Spring Framework 6.1, resource location patterns are also supported — for +example, `"classpath*:/config/*.properties"`. The following example uses a test properties file: diff --git a/spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java b/spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java index 292ff74fe018..86c90a80402f 100644 --- a/spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java +++ b/spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java @@ -110,9 +110,10 @@ /** * 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}. + *

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). *

Supported File Formats

*

By default, both traditional and XML-based properties file formats are * supported — for example, {@code "classpath:/com/example/test.properties"} @@ -130,11 +131,19 @@ * {@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. **/*.properties) - * are not permitted: each location must evaluate to exactly one properties - * resource. Property placeholders in paths (i.e., ${...}) will be - * {@linkplain org.springframework.core.env.Environment#resolveRequiredPlaceholders(String) resolved} - * against the {@code Environment}. + *

Property placeholders in paths (i.e., ${...}) will be + * {@linkplain org.springframework.core.env.Environment#resolveRequiredPlaceholders(String) + * resolved} against the {@code Environment}. + *

As of Spring Framework 6.1, resource location patterns are also + * supported — for example, {@code "classpath*:/config/*.properties"}. + *

WARNING: a pattern such as {@code "classpath*:/config/*.properties"} + * may be effectively equivalent to an explicit enumeration of resource locations such as + * {"classpath:/config/mail.properties", classpath:/config/order.properties"}; + * 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. *

Default Properties File Detection

*

See the class-level Javadoc for a discussion on detection of defaults. *

Precedence

diff --git a/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceUtils.java b/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceUtils.java index 67d61cb51c0a..46460041fe33 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceUtils.java @@ -47,6 +47,8 @@ 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 static void addPropertiesFilesToEnvironment(ConfigurableApplicationContex *

Property placeholders in resource locations (i.e., ${...}) * will be {@linkplain Environment#resolveRequiredPlaceholders(String) resolved} * against the {@code Environment}. + *

A {@link ResourcePatternResolver} will be used to resolve resource + * location patterns into multiple resource locations. *

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 static void addPropertySourcesToEnvironment(ConfigurableApplicationContex *

Property placeholders in resource locations (i.e., ${...}) * will be {@linkplain Environment#resolveRequiredPlaceholders(String) resolved} * against the {@code Environment}. + *

A {@link ResourcePatternResolver} will be used to resolve resource + * location patterns into multiple resource locations. *

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 static void addPropertySourcesToEnvironment(ConfigurableEnvironment envir 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 static void addPropertySourcesToEnvironment(ConfigurableEnvironment envir 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); + } } } } diff --git a/spring-test/src/test/java/org/springframework/test/context/env/ResourcePatternExplicitPropertiesFileTests.java b/spring-test/src/test/java/org/springframework/test/context/env/ResourcePatternExplicitPropertiesFileTests.java new file mode 100644 index 000000000000..2e85f89a56d3 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/env/ResourcePatternExplicitPropertiesFileTests.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.env; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link TestPropertySource @TestPropertySource} support + * for resource patterns for resource locations. + * + * @author Sam Brannen + * @since 6.1 + */ +@SpringJUnitConfig +@TestPropertySource("classpath*:/org/springframework/test/context/env/pattern/file?.properties") +class ResourcePatternExplicitPropertiesFileTests { + + @Test + void verifyPropertiesAreAvailableInEnvironment(@Autowired Environment env) { + assertEnvironmentContainsProperties(env, "from.p1", "from.p2", "from.p3"); + } + + + private static void assertEnvironmentContainsProperties(Environment env, String... names) { + for (String name : names) { + assertThat(env.containsProperty(name)).as("environment contains property '%s'", name).isTrue(); + } + } + + + @Configuration + static class Config { + /* no user beans required for these tests */ + } + +} diff --git a/spring-test/src/test/resources/org/springframework/test/context/env/pattern/file1.properties b/spring-test/src/test/resources/org/springframework/test/context/env/pattern/file1.properties new file mode 100644 index 000000000000..e9a2a13ffccb --- /dev/null +++ b/spring-test/src/test/resources/org/springframework/test/context/env/pattern/file1.properties @@ -0,0 +1 @@ +from.p1 = 1 diff --git a/spring-test/src/test/resources/org/springframework/test/context/env/pattern/file2.properties b/spring-test/src/test/resources/org/springframework/test/context/env/pattern/file2.properties new file mode 100644 index 000000000000..72048c661c77 --- /dev/null +++ b/spring-test/src/test/resources/org/springframework/test/context/env/pattern/file2.properties @@ -0,0 +1 @@ +from.p2 = 2 diff --git a/spring-test/src/test/resources/org/springframework/test/context/env/pattern/file3.properties b/spring-test/src/test/resources/org/springframework/test/context/env/pattern/file3.properties new file mode 100644 index 000000000000..af95803fa730 --- /dev/null +++ b/spring-test/src/test/resources/org/springframework/test/context/env/pattern/file3.properties @@ -0,0 +1 @@ +from.p3 = 3