Skip to content

Commit 6cdb344

Browse files
committed
Register DynamicPropertyRegistry as singleton bean in test ApplicationContext
Prior to this commit, DynamicPropertyRegistry could only be used with a static @⁠DynamicPropertySource method in an integration test class; however, it can also be useful to be able to register a "dynamic property" from within a test's ApplicationContext -- for example, in a @⁠Bean method in a @⁠Configuration class that is specific to testing scenarios. To support such use cases, this commit updates the dynamic property source infrastructure so that a DynamicPropertyRegistry is always registered as a singleton bean in a test's ApplicationContext. This allows DynamicPropertyRegistry to be autowired into a @⁠Configuration class or supplied to a @⁠Bean method as an argument as shown in the following example. @⁠Bean @⁠DynamicPropertySource ApiServer apiServer(DynamicPropertyRegistry registry) { ApiServer apiServer = new ApiServer(); registry.add("api.url", apiServer::getUrl); return apiServer; } Note that the use of @⁠DynamicPropertySource on the @⁠Bean method is optional and results in the corresponding bean being eagerly initialized so that other singleton beans in the context can be given access to the dynamic properties sourced from that bean when those other beans are initialized. Side note: DynamicPropertySourceBeanInitializer temporarily implements LoadTimeWeaverAware since doing so is currently the only way to have a component eagerly initialized before the ConfigurableListableBeanFactory.preInstantiateSingletons() phase. However, we plan to introduce a first-class callback to support such use cases in the future. Closes gh-32271
1 parent 7d8279a commit 6cdb344

15 files changed

+507
-89
lines changed

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

+88-25
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,58 @@
11
[[testcontext-ctx-management-dynamic-property-sources]]
22
= Context Configuration with Dynamic Property Sources
33

4-
As of Spring Framework 5.2.5, the TestContext framework provides support for _dynamic_
5-
properties via the `@DynamicPropertySource` annotation. This annotation can be used in
6-
integration tests that need to add properties with dynamic values to the set of
7-
`PropertySources` in the `Environment` for the `ApplicationContext` loaded for the
8-
integration test.
4+
The Spring TestContext Framework provides support for _dynamic_ properties via the
5+
`@DynamicPropertySource` annotation and the `DynamicPropertyRegistry`.
96

107
[NOTE]
118
====
12-
The `@DynamicPropertySource` annotation and its supporting infrastructure were
13-
originally designed to allow properties from
14-
{testcontainers-site}[Testcontainers] based tests to be exposed easily to
15-
Spring integration tests. However, this feature may also be used with any form of
16-
external resource whose lifecycle is maintained outside the test's `ApplicationContext`.
9+
The `@DynamicPropertySource` annotation and its supporting infrastructure were originally
10+
designed to allow properties from {testcontainers-site}[Testcontainers] based tests to be
11+
exposed easily to Spring integration tests. However, this feature may be used with any
12+
form of external resource whose lifecycle is managed outside the test's
13+
`ApplicationContext` or with beans whose lifecycle is managed by the test's
14+
`ApplicationContext`.
1715
====
1816

19-
In contrast to the xref:testing/testcontext-framework/ctx-management/property-sources.adoc[`@TestPropertySource`]
20-
annotation that is applied at the class level, `@DynamicPropertySource` must be applied
21-
to a `static` method that accepts a single `DynamicPropertyRegistry` argument which is
22-
used to add _name-value_ pairs to the `Environment`. Values are dynamic and provided via
23-
a `Supplier` which is only invoked when the property is resolved. Typically, method
24-
references are used to supply values, as can be seen in the following example which uses
25-
the Testcontainers project to manage a Redis container outside of the Spring
26-
`ApplicationContext`. The IP address and port of the managed Redis container are made
27-
available to components within the test's `ApplicationContext` via the `redis.host` and
28-
`redis.port` properties. These properties can be accessed via Spring's `Environment`
29-
abstraction or injected directly into Spring-managed components – for example, via
30-
`@Value("${redis.host}")` and `@Value("${redis.port}")`, respectively.
17+
In contrast to the
18+
xref:testing/testcontext-framework/ctx-management/property-sources.adoc[`@TestPropertySource`]
19+
annotation that is applied at the class level, `@DynamicPropertySource` can be applied to
20+
`static` methods in integration test classes or to `@Bean` methods in test
21+
`@Configuration` classes in order to add properties with dynamic values to the set of
22+
`PropertySources` in the `Environment` for the `ApplicationContext` loaded for the
23+
integration test.
24+
25+
A `DynamicPropertyRegistry` is used to add _name-value_ pairs to the `Environment`.
26+
Values are dynamic and provided via a `Supplier` which is only invoked when the property
27+
is resolved. Typically, method references are used to supply values.
28+
29+
Methods in integration test classes that are annotated with `@DynamicPropertySource` must
30+
be `static` and must accept a single `DynamicPropertyRegistry` argument.
31+
32+
`@Bean` methods annotated with `@DynamicPropertySource` may either accept an argument of
33+
type `DynamicPropertyRegistry` or access a `DynamicPropertyRegistry` instance autowired
34+
into their enclosing `@Configuration` class. Note, however, that `@Bean` methods which
35+
interact with a `DynamicPropertyRegistry` are not required to be annotated with
36+
`@DynamicPropertySource` unless they need to enforce eager initialization of the bean
37+
within the context. See the class-level javadoc for `DynamicPropertyRegistry` for details.
3138

3239
[TIP]
3340
====
3441
If you use `@DynamicPropertySource` in a base class and discover that tests in subclasses
3542
fail because the dynamic properties change between subclasses, you may need to annotate
36-
your base class with xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[`@DirtiesContext`] to
37-
ensure that each subclass gets its own `ApplicationContext` with the correct dynamic
43+
your base class with
44+
xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[`@DirtiesContext`]
45+
to ensure that each subclass gets its own `ApplicationContext` with the correct dynamic
3846
properties.
3947
====
4048

49+
The following example uses the Testcontainers project to manage a Redis container outside
50+
of the Spring `ApplicationContext`. The IP address and port of the managed Redis
51+
container are made available to components within the test's `ApplicationContext` via the
52+
`redis.host` and `redis.port` properties. These properties can be accessed via Spring's
53+
`Environment` abstraction or injected directly into Spring-managed components – for
54+
example, via `@Value("${redis.host}")` and `@Value("${redis.port}")`, respectively.
55+
4156
[tabs]
4257
======
4358
Java::
@@ -92,7 +107,55 @@ Kotlin::
92107
----
93108
======
94109

95-
[[precedence]]
110+
The following example demonstrates how to use `DynamicPropertyRegistry` and
111+
`@DynamicPropertySource` with a `@Bean` method. The `api.url` property can be accessed
112+
via Spring's `Environment` abstraction or injected directly into other Spring-managed
113+
components – for example, via `@Value("${api.url}")`. The value of the `api.url` property
114+
will be dynamically retrieved from the `ApiServer` bean.
115+
116+
[tabs]
117+
======
118+
Java::
119+
+
120+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
121+
----
122+
@Configuration
123+
class TestConfig {
124+
125+
@Bean
126+
@DynamicPropertySource
127+
ApiServer apiServer(DynamicPropertyRegistry registry) {
128+
ApiServer apiServer = new ApiServer();
129+
registry.add("api.url", apiServer::getUrl);
130+
return apiServer;
131+
}
132+
}
133+
----
134+
135+
Kotlin::
136+
+
137+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
138+
----
139+
@Configuration
140+
class TestConfig {
141+
142+
@Bean
143+
@DynamicPropertySource
144+
fun apiServer(registry: DynamicPropertyRegistry): ApiServer {
145+
val apiServer = ApiServer()
146+
registry.add("api.url", apiServer::getUrl)
147+
return apiServer
148+
}
149+
}
150+
----
151+
======
152+
153+
NOTE: The use of `@DynamicPropertySource` on the `@Bean` method is optional and results
154+
in the `ApiServer` bean being eagerly initialized so that other beans in the context can
155+
be given access to the dynamic properties sourced from the `ApiServer` bean when those
156+
other beans are initialized.
157+
158+
[[testcontext-ctx-management-dynamic-property-sources-precedence]]
96159
== Precedence
97160

98161
Dynamic properties have higher precedence than those loaded from `@TestPropertySource`,

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,6 @@ meta-present `@TestPropertySource` annotations. In other words, `locations` and
184184
meta-annotation.
185185
====
186186

187-
188187
[[default-properties-file-detection]]
189188
== Default Properties File Detection
190189

@@ -195,7 +194,7 @@ if the annotated test class is `com.example.MyTest`, the corresponding default p
195194
file is `classpath:com/example/MyTest.properties`. If the default cannot be detected, an
196195
`IllegalStateException` is thrown.
197196

198-
[[precedence]]
197+
[[testcontext-ctx-management-property-sources-precedence]]
199198
== Precedence
200199

201200
Test properties have higher precedence than those defined in the operating system's

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

+20-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,9 +19,25 @@
1919
import java.util.function.Supplier;
2020

2121
/**
22-
* Registry used with {@link DynamicPropertySource @DynamicPropertySource}
23-
* methods so that they can add properties to the {@code Environment} that have
24-
* dynamically resolved values.
22+
* Registry that is used to add properties with dynamically resolved values to
23+
* the {@code Environment}.
24+
*
25+
* <p>A {@code DynamicPropertyRegistry} is supplied as an argument to static
26+
* {@link DynamicPropertySource @DynamicPropertySource} methods in integration
27+
* test classes.
28+
*
29+
* <p>As of Spring Framework 6.2, a {@code DynamicPropertyRegistry} is also
30+
* registered as a singleton bean in the test's {@code ApplicationContext}. This
31+
* allows a {@code DynamicPropertyRegistry} to be autowired into a
32+
* {@code @Configuration} class or supplied to a {@code @Bean} method as an
33+
* argument, making it possible to register a dynamic property from within a test's
34+
* {@code ApplicationContext}. For example, a {@code @Bean} method can register
35+
* a property whose value is dynamically sourced from the bean that the method
36+
* returns. Note that such a {@code @Bean} method can optionally be annotated
37+
* with {@code @DynamicPropertySource} to enforce eager initialization of the
38+
* bean within the context, thereby ensuring that any dynamic properties sourced
39+
* from that bean are available to other singleton beans within the context.
40+
* See {@link DynamicPropertySource @DynamicPropertySource} for an example.
2541
*
2642
* @author Phillip Webb
2743
* @author Sam Brannen

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

+54-17
Original file line numberDiff line numberDiff line change
@@ -23,29 +23,43 @@
2323
import java.lang.annotation.Target;
2424

2525
/**
26-
* {@code @DynamicPropertySource} is an annotation that can be applied to methods
27-
* in integration test classes that need to add properties with dynamic values to
28-
* the {@code Environment}'s set of {@code PropertySources}.
26+
* {@code @DynamicPropertySource} is an annotation that can be applied to static
27+
* methods in integration test classes or to {@code @Bean} methods in test
28+
* {@code @Configuration} classes in order to add properties with dynamic values
29+
* to the {@code Environment}'s set of {@code PropertySources}.
2930
*
3031
* <p>This annotation and its supporting infrastructure were originally designed
3132
* to allow properties from
3233
* <a href="https://www.testcontainers.org/">Testcontainers</a> based tests to be
33-
* exposed easily to Spring integration tests. However, this feature may also be
34-
* used with any form of external resource whose lifecycle is maintained outside
34+
* exposed easily to Spring integration tests. However, this feature may be used
35+
* with any form of external resource whose lifecycle is managed outside the
36+
* test's {@code ApplicationContext} or with beans whose lifecycle is managed by
3537
* the test's {@code ApplicationContext}.
3638
*
37-
* <p>Methods annotated with {@code @DynamicPropertySource} must be {@code static}
38-
* and must have a single {@link DynamicPropertyRegistry} argument which is used
39-
* to add <em>name-value</em> pairs to the {@code Environment}'s set of
40-
* {@code PropertySources}. Values are dynamic and provided via a
41-
* {@link java.util.function.Supplier} which is only invoked when the property
42-
* is resolved. Typically, method references are used to supply values, as in the
43-
* example below.
39+
* <p>{@code @DynamicPropertySource}-annotated methods use a
40+
* {@code DynamicPropertyRegistry} to add <em>name-value</em> pairs to the
41+
* {@code Environment}'s set of {@code PropertySources}. Values are dynamic and
42+
* provided via a {@link java.util.function.Supplier} which is only invoked when
43+
* the property is resolved. Typically, method references are used to supply values,
44+
* as in the example below.
4445
*
45-
* <p>As of Spring Framework 5.3.2, dynamic properties from methods annotated with
46-
* {@code @DynamicPropertySource} will be <em>inherited</em> from enclosing test
47-
* classes, analogous to inheritance from superclasses and interfaces. See
48-
* {@link NestedTestConfiguration @NestedTestConfiguration} for details.
46+
* <p>Methods in integration test classes that are annotated with
47+
* {@code @DynamicPropertySource} must be {@code static} and must accept a single
48+
* {@link DynamicPropertyRegistry} argument.
49+
*
50+
* <p>{@code @Bean} methods annotated with {@code @DynamicPropertySource} may
51+
* either accept an argument of type {@code DynamicPropertyRegistry} or access a
52+
* {@code DynamicPropertyRegistry} instance autowired into their enclosing
53+
* {@code @Configuration} class. Note, however, that {@code @Bean} methods which
54+
* interact with a {@code DynamicPropertyRegistry} are not required to be annotated
55+
* with {@code @DynamicPropertySource} unless they need to enforce eager
56+
* initialization of the bean within the context.
57+
* See {@link DynamicPropertyRegistry} for details.
58+
*
59+
* <p>Dynamic properties from methods annotated with {@code @DynamicPropertySource}
60+
* will be <em>inherited</em> from enclosing test classes, analogous to inheritance
61+
* from superclasses and interfaces.
62+
* See {@link NestedTestConfiguration @NestedTestConfiguration} for details.
4963
*
5064
* <p><strong>NOTE</strong>: if you use {@code @DynamicPropertySource} in a base
5165
* class and discover that tests in subclasses fail because the dynamic properties
@@ -64,7 +78,13 @@
6478
* override properties loaded via {@code @TestPropertySource}, system property
6579
* sources, and application property sources.
6680
*
67-
* <h3>Example</h3>
81+
* <h3>Examples</h3>
82+
*
83+
* <p>The following example demonstrates how to use {@code @DynamicPropertySource}
84+
* in an integration test class. Beans in the {@code ApplicationContext} can
85+
* access the {@code redis.host} and {@code redis.port} properties which are
86+
* dynamically retrieved from the Redis container.
87+
*
6888
* <pre class="code">
6989
* &#064;SpringJUnitConfig(...)
7090
* &#064;Testcontainers
@@ -81,7 +101,24 @@
81101
* registry.add("redis.host", redis::getHost);
82102
* registry.add("redis.port", redis::getFirstMappedPort);
83103
* }
104+
* }</pre>
84105
*
106+
* <p>The following example demonstrates how to use {@code @DynamicPropertySource}
107+
* with a {@code @Bean} method. Beans in the {@code ApplicationContext} can
108+
* access the {@code api.url} property which is dynamically retrieved from the
109+
* {@code ApiServer} bean.
110+
*
111+
* <pre class="code">
112+
* &#064;Configuration
113+
* class TestConfig {
114+
*
115+
* &#064;Bean
116+
* &#064;DynamicPropertySource
117+
* ApiServer apiServer(DynamicPropertyRegistry registry) {
118+
* ApiServer apiServer = new ApiServer();
119+
* registry.add("api.url", apiServer::getUrl);
120+
* return apiServer;
121+
* }
85122
* }</pre>
86123
*
87124
* @author Phillip Webb

0 commit comments

Comments
 (0)