From a842434bff92ec7ceced0a43ac88b3b4c7a6df2f Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Wed, 1 Apr 2020 17:56:58 +0200 Subject: [PATCH] Document precedence for @DynamicPropertySource Closes gh-24837 --- .../test/context/DynamicPropertySource.java | 11 +- .../test/context/TestPropertySource.java | 4 +- ...DynamicPropertySourceIntegrationTests.java | 44 ++++++-- src/docs/asciidoc/testing.adoc | 102 ++++++++++++++---- 4 files changed, 135 insertions(+), 26 deletions(-) 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. * - *

Example

+ *

Precedence

+ *

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

Example

*
  * @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`)