diff --git a/spring-test/src/main/java/org/springframework/test/context/DynamicPropertySource.java b/spring-test/src/main/java/org/springframework/test/context/DynamicPropertySource.java index 69bd5d41bf..b2c9f27a5c 100644 --- a/spring-test/src/main/java/org/springframework/test/context/DynamicPropertySource.java +++ b/spring-test/src/main/java/org/springframework/test/context/DynamicPropertySource.java @@ -41,8 +41,17 @@ import java.lang.annotation.Target; * is resolved. Typically, method references are used to supply values, as in the * following example. * - *
Dynamic properties have higher precedence than those loaded from + * {@link TestPropertySource @TestPropertySource}, the operating system's + * environment, Java system properties, or property sources added by the + * application declaratively by using + * {@link org.springframework.context.annotation.PropertySource @PropertySource} + * or programmatically. Thus, dynamic properties can be used to selectively + * override properties loaded via {@code @TestPropertySource}, system property + * sources, and application property sources. * + *
* @SpringJUnitConfig(...)
* @Testcontainers
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 84ebc26bad..6315da3851 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
@@ -44,7 +44,9 @@ import org.springframework.core.annotation.AliasFor;
* or some other means). Thus, test property sources can be used to selectively
* override properties defined in system and application property sources.
* Furthermore, inlined {@link #properties} have higher precedence than
- * properties loaded from resource {@link #locations}.
+ * properties loaded from resource {@link #locations}. Note, however, that
+ * properties registered via {@link DynamicPropertySource @DynamicPropertySource}
+ * have higher precedence than those loaded via {@code @TestPropertySource}.
*
* Default Properties File Detection
* If {@code @TestPropertySource} is declared as an empty annotation
diff --git a/spring-test/src/test/java/org/springframework/test/context/DynamicPropertySourceIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/DynamicPropertySourceIntegrationTests.java
index 345cd38fa7..424edde7bc 100644
--- a/spring-test/src/test/java/org/springframework/test/context/DynamicPropertySourceIntegrationTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/DynamicPropertySourceIntegrationTests.java
@@ -16,38 +16,71 @@
package org.springframework.test.context;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
-import org.springframework.stereotype.Component;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.MutablePropertySources;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
/**
- * Integration test for {@link DynamicPropertySource @DynamicPropertySource}.
+ * Integration tests for {@link DynamicPropertySource @DynamicPropertySource}.
*
* @author Phillip Webb
* @author Sam Brannen
*/
@SpringJUnitConfig
+@TestPropertySource(properties = "test.container.ip: test")
+@TestInstance(PER_CLASS)
+@DisplayName("@DynamicPropertySource integration tests")
class DynamicPropertySourceIntegrationTests {
+ private static final String TEST_CONTAINER_IP = "test.container.ip";
+
+ static {
+ System.setProperty(TEST_CONTAINER_IP, "system");
+ }
+
static DemoContainer container = new DemoContainer();
-
@DynamicPropertySource
static void containerProperties(DynamicPropertyRegistry registry) {
- registry.add("test.container.ip", container::getIpAddress);
+ registry.add(TEST_CONTAINER_IP, container::getIpAddress);
registry.add("test.container.port", container::getPort);
}
+ @AfterAll
+ void clearSystemProperty() {
+ System.clearProperty(TEST_CONTAINER_IP);
+ }
+
@Test
- void hasInjectedValues(@Autowired Service service) {
+ @DisplayName("@DynamicPropertySource overrides @TestPropertySource and JVM system property")
+ void dynamicPropertySourceOverridesTestPropertySourceAndSystemProperty(@Autowired ConfigurableEnvironment env) {
+ MutablePropertySources propertySources = env.getPropertySources();
+ assertThat(propertySources.size()).isGreaterThanOrEqualTo(4);
+ assertThat(propertySources.contains("Dynamic Test Properties")).isTrue();
+ assertThat(propertySources.contains("Inlined Test Properties")).isTrue();
+ assertThat(propertySources.contains("systemProperties")).isTrue();
+ assertThat(propertySources.get("Dynamic Test Properties").getProperty(TEST_CONTAINER_IP)).isEqualTo("127.0.0.1");
+ assertThat(propertySources.get("Inlined Test Properties").getProperty(TEST_CONTAINER_IP)).isEqualTo("test");
+ assertThat(propertySources.get("systemProperties").getProperty(TEST_CONTAINER_IP)).isEqualTo("system");
+ assertThat(env.getProperty(TEST_CONTAINER_IP)).isEqualTo("127.0.0.1");
+ }
+
+ @Test
+ @DisplayName("@Service has values injected from @DynamicPropertySource")
+ void serviceHasInjectedValues(@Autowired Service service) {
assertThat(service.getIp()).isEqualTo("127.0.0.1");
assertThat(service.getPort()).isEqualTo(4242);
}
@@ -58,7 +91,6 @@ class DynamicPropertySourceIntegrationTests {
static class Config {
}
- @Component
static class Service {
private final String ip;
diff --git a/src/docs/asciidoc/testing.adoc b/src/docs/asciidoc/testing.adoc
index 0c9360711e..2a70e1e446 100644
--- a/src/docs/asciidoc/testing.adoc
+++ b/src/docs/asciidoc/testing.adoc
@@ -411,6 +411,7 @@ Spring's testing annotations include the following:
* <>
* <>
* <>
+* <>
* <>
* <>
* <>
@@ -758,13 +759,6 @@ locations of properties files and inlined properties to be added to the set of
`PropertySources` in the `Environment` for an `ApplicationContext` loaded for an
integration test.
-Test property sources have higher precedence than those loaded from the operating
-system's environment or Java system properties as well as property sources added by the
-application declaratively through `@PropertySource` or programmatically. Thus, test
-property sources can be used to selectively override properties defined in system and
-application property sources. Furthermore, inlined properties have higher precedence than
-properties loaded from resource locations.
-
The following example demonstrates how to declare a properties file from the classpath:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
@@ -816,6 +810,65 @@ The following example demonstrates how to declare inlined properties:
See <> for examples and further details.
+[[spring-testing-annotation-dynamicpropertysource]]
+===== `@DynamicPropertySource`
+
+`@DynamicPropertySource` is a method-level annotation that you can use to register
+_dynamic_ properties to be added to the set of `PropertySources` in the `Environment` for
+an `ApplicationContext` loaded for an integration test. Dynamic properties are useful
+when you do not know the value of the properties upfront – for example, if the properties
+are managed by an external resource such as for a container managed by the
+https://www.testcontainers.org/[Testcontainers] project.
+
+The following example demonstrates how to register a dynamic property:
+
+[source,java,indent=0,subs="verbatim,quotes",role="primary"]
+.Java
+----
+ @ContextConfiguration
+ class MyIntegrationTests {
+
+ static MyExternalServer server = // ...
+
+ @DynamicPropertySource // <1>
+ static void dynamicProperties(DynamicPropertyRegistry registry) { // <2>
+ registry.add("server.port", server::getPort); // <3>
+ }
+
+ // tests ...
+ }
+----
+<1> Annotate a `static` method with `@DynamicPropertySource`.
+<2> Accept a `DynamicPropertyRegistry` as an argument.
+<3> Register a dynamic `server.port` property to be retrieved lazily from the server.
+
+[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
+.Kotlin
+----
+ @ContextConfiguration
+ class MyIntegrationTests {
+
+ companion object {
+
+ @JvmStatic
+ val server: MyExternalServer = // ...
+
+ @DynamicPropertySource // <1>
+ @JvmStatic
+ fun dynamicProperties(registry: DynamicPropertyRegistry) { // <2>
+ registry.add("server.port", server::getPort) // <3>
+ }
+ }
+
+ // tests ...
+ }
+----
+<1> Annotate a `static` method with `@DynamicPropertySource`.
+<2> Accept a `DynamicPropertyRegistry` as an argument.
+<3> Register a dynamic `server.port` property to be retrieved lazily from the server.
+
+See <> for further details.
+
[[spring-testing-annotation-dirtiescontext]]
===== `@DirtiesContext`
@@ -3835,12 +3888,14 @@ file is `classpath:com/example/MyTest.properties`. If the default cannot be dete
====== Precedence
-Test property sources have higher precedence than those loaded from the operating
-system's environment, Java system properties, or property sources added by the
-application declaratively by using `@PropertySource` or programmatically. Thus, test
-property sources can be used to selectively override properties defined in system and
-application property sources. Furthermore, inlined properties have higher precedence than
-properties loaded from resource locations.
+Test properties have higher precedence than those defined in the operating system's
+environment, Java system properties, or property sources added by the application
+declaratively by using `@PropertySource` or programmatically. Thus, test properties can
+be used to selectively override properties loaded from system and application property
+sources. Furthermore, inlined properties have higher precedence than properties loaded
+from resource locations. Note, however, that properties registered via
+<> have
+higher precedence than those loaded via `@TestPropertySource`.
In the next example, the `timezone` and `port` properties and any properties defined in
`"/test.properties"` override any properties of the same name that are defined in system
@@ -3968,8 +4023,8 @@ to define properties in both a subclass and its superclass by using inline prope
===== Context Configuration with Dynamic Property Sources
As of Spring Framework 5.2.5, the TestContext framework provides support for _dynamic_
-property sources via the `@DynamicPropertySource` annotation. This annotation can be used
-in integration tests that need to add properties with dynamic values to the set of
+properties via the `@DynamicPropertySource` annotation. This annotation can be used in
+integration tests that need to add properties with dynamic values to the set of
`PropertySources` in the `Environment` for the `ApplicationContext` loaded for the
integration test.
@@ -3988,8 +4043,9 @@ references are used to supply values, as can be seen in the following example wh
the Testcontainers project to manage a Redis container outside of the Spring
`ApplicationContext`. The IP address and port of the managed Redis container are made
available to components within the test's `ApplicationContext` via the `redis.host` and
-`redis.port` properties. These properties can be injected into Spring-managed components
-via `@Value("${redis.host}")` and `@Value("${redis.port}")`, respectively.
+`redis.port` properties. These properties can be accessed via Spring's `Environment`
+abstraction or injected directly into Spring-managed components – for example, via
+`@Value("${redis.host}")` and `@Value("${redis.port}")`, respectively.
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
@@ -4037,6 +4093,14 @@ via `@Value("${redis.host}")` and `@Value("${redis.port}")`, respectively.
}
----
+====== Precedence
+
+Dynamic properties have higher precedence than those loaded from `@TestPropertySource`,
+the operating system's environment, Java system properties, or property sources added by
+the application declaratively by using `@PropertySource` or programmatically. Thus,
+dynamic properties can be used to selectively override properties loaded via
+`@TestPropertySource`, system property sources, and application property sources.
+
[[testcontext-ctx-management-web]]
===== Loading a `WebApplicationContext`
@@ -4272,7 +4336,9 @@ framework uses the following configuration parameters to build the context cache
* `locations` (from `@ContextConfiguration`)
* `classes` (from `@ContextConfiguration`)
* `contextInitializerClasses` (from `@ContextConfiguration`)
-* `contextCustomizers` (from `ContextCustomizerFactory`)
+* `contextCustomizers` (from `ContextCustomizerFactory`) – this includes
+ `@DynamicPropertySource` methods as well as various features from Spring Boot's
+ testing support such as `@MockBean` and `@SpyBean`.
* `contextLoader` (from `@ContextConfiguration`)
* `parent` (from `@ContextHierarchy`)
* `activeProfiles` (from `@ActiveProfiles`)