diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/dynamic-property-sources.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/dynamic-property-sources.adoc
index 4705d57cbe6..a3936d68824 100644
--- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/dynamic-property-sources.adoc
+++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/dynamic-property-sources.adoc
@@ -1,43 +1,58 @@
[[testcontext-ctx-management-dynamic-property-sources]]
= Context Configuration with Dynamic Property Sources
-As of Spring Framework 5.2.5, the TestContext framework provides support for _dynamic_
-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.
+The Spring TestContext Framework provides support for _dynamic_ properties via the
+`@DynamicPropertySource` annotation and the `DynamicPropertyRegistry`.
[NOTE]
====
-The `@DynamicPropertySource` annotation and its supporting infrastructure were
-originally designed to allow properties from
-{testcontainers-site}[Testcontainers] based tests to be exposed easily to
-Spring integration tests. However, this feature may also be used with any form of
-external resource whose lifecycle is maintained outside the test's `ApplicationContext`.
+The `@DynamicPropertySource` annotation and its supporting infrastructure were originally
+designed to allow properties from {testcontainers-site}[Testcontainers] based tests to be
+exposed easily to Spring integration tests. However, this feature may be used with any
+form of external resource whose lifecycle is managed outside the test's
+`ApplicationContext` or with beans whose lifecycle is managed by the test's
+`ApplicationContext`.
====
-In contrast to the xref:testing/testcontext-framework/ctx-management/property-sources.adoc[`@TestPropertySource`]
-annotation that is applied at the class level, `@DynamicPropertySource` must be applied
-to a `static` method that accepts a single `DynamicPropertyRegistry` argument which is
-used to add _name-value_ pairs to the `Environment`. Values are dynamic and provided via
-a `Supplier` which is only invoked when the property is resolved. Typically, method
-references are used to supply values, as can be seen in the following example which uses
-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 accessed via Spring's `Environment`
-abstraction or injected directly into Spring-managed components – for example, via
-`@Value("${redis.host}")` and `@Value("${redis.port}")`, respectively.
+In contrast to the
+xref:testing/testcontext-framework/ctx-management/property-sources.adoc[`@TestPropertySource`]
+annotation that is applied at the class level, `@DynamicPropertySource` can be applied to
+`static` methods in integration test classes or to `@Bean` methods in test
+`@Configuration` classes in order to add properties with dynamic values to the set of
+`PropertySources` in the `Environment` for the `ApplicationContext` loaded for the
+integration test.
+
+A `DynamicPropertyRegistry` is used to add _name-value_ pairs to the `Environment`.
+Values are dynamic and provided via a `Supplier` which is only invoked when the property
+is resolved. Typically, method references are used to supply values.
+
+Methods in integration test classes that are annotated with `@DynamicPropertySource` must
+be `static` and must accept a single `DynamicPropertyRegistry` argument.
+
+`@Bean` methods annotated with `@DynamicPropertySource` may either accept an argument of
+type `DynamicPropertyRegistry` or access a `DynamicPropertyRegistry` instance autowired
+into their enclosing `@Configuration` class. Note, however, that `@Bean` methods which
+interact with a `DynamicPropertyRegistry` are not required to be annotated with
+`@DynamicPropertySource` unless they need to enforce eager initialization of the bean
+within the context. See the class-level javadoc for `DynamicPropertyRegistry` for details.
[TIP]
====
If you use `@DynamicPropertySource` in a base class and discover that tests in subclasses
fail because the dynamic properties change between subclasses, you may need to annotate
-your base class with xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[`@DirtiesContext`] to
-ensure that each subclass gets its own `ApplicationContext` with the correct dynamic
+your base class with
+xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[`@DirtiesContext`]
+to ensure that each subclass gets its own `ApplicationContext` with the correct dynamic
properties.
====
+The following example uses 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 accessed via Spring's
+`Environment` abstraction or injected directly into Spring-managed components – for
+example, via `@Value("${redis.host}")` and `@Value("${redis.port}")`, respectively.
+
[tabs]
======
Java::
@@ -92,7 +107,55 @@ Kotlin::
----
======
-[[precedence]]
+The following example demonstrates how to use `DynamicPropertyRegistry` and
+`@DynamicPropertySource` with a `@Bean` method. The `api.url` property can be accessed
+via Spring's `Environment` abstraction or injected directly into other Spring-managed
+components – for example, via `@Value("${api.url}")`. The value of the `api.url` property
+will be dynamically retrieved from the `ApiServer` bean.
+
+[tabs]
+======
+Java::
++
+[source,java,indent=0,subs="verbatim,quotes",role="primary"]
+----
+ @Configuration
+ class TestConfig {
+
+ @Bean
+ @DynamicPropertySource
+ ApiServer apiServer(DynamicPropertyRegistry registry) {
+ ApiServer apiServer = new ApiServer();
+ registry.add("api.url", apiServer::getUrl);
+ return apiServer;
+ }
+ }
+----
+
+Kotlin::
++
+[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
+----
+ @Configuration
+ class TestConfig {
+
+ @Bean
+ @DynamicPropertySource
+ fun apiServer(registry: DynamicPropertyRegistry): ApiServer {
+ val apiServer = ApiServer()
+ registry.add("api.url", apiServer::getUrl)
+ return apiServer
+ }
+ }
+----
+======
+
+NOTE: The use of `@DynamicPropertySource` on the `@Bean` method is optional and results
+in the `ApiServer` bean being eagerly initialized so that other beans in the context can
+be given access to the dynamic properties sourced from the `ApiServer` bean when those
+other beans are initialized.
+
+[[testcontext-ctx-management-dynamic-property-sources-precedence]]
== Precedence
Dynamic properties have higher precedence than those loaded from `@TestPropertySource`,
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 34057860b85..bb83c4d4da6 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
@@ -184,7 +184,6 @@ meta-present `@TestPropertySource` annotations. In other words, `locations` and
meta-annotation.
====
-
[[default-properties-file-detection]]
== Default Properties File Detection
@@ -195,7 +194,7 @@ if the annotated test class is `com.example.MyTest`, the corresponding default p
file is `classpath:com/example/MyTest.properties`. If the default cannot be detected, an
`IllegalStateException` is thrown.
-[[precedence]]
+[[testcontext-ctx-management-property-sources-precedence]]
== Precedence
Test properties have higher precedence than those defined in the operating system's
diff --git a/spring-test/src/main/java/org/springframework/test/context/DynamicPropertyRegistry.java b/spring-test/src/main/java/org/springframework/test/context/DynamicPropertyRegistry.java
index 2f521c4925d..cc73b838a5b 100644
--- a/spring-test/src/main/java/org/springframework/test/context/DynamicPropertyRegistry.java
+++ b/spring-test/src/main/java/org/springframework/test/context/DynamicPropertyRegistry.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2024 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.
@@ -19,9 +19,25 @@ package org.springframework.test.context;
import java.util.function.Supplier;
/**
- * Registry used with {@link DynamicPropertySource @DynamicPropertySource}
- * methods so that they can add properties to the {@code Environment} that have
- * dynamically resolved values.
+ * Registry that is used to add properties with dynamically resolved values to
+ * the {@code Environment}.
+ *
+ *
A {@code DynamicPropertyRegistry} is supplied as an argument to static
+ * {@link DynamicPropertySource @DynamicPropertySource} methods in integration
+ * test classes.
+ *
+ *
As of Spring Framework 6.2, a {@code DynamicPropertyRegistry} is also
+ * registered as a singleton bean in the test's {@code ApplicationContext}. This
+ * allows a {@code DynamicPropertyRegistry} to be autowired into a
+ * {@code @Configuration} class or supplied to a {@code @Bean} method as an
+ * argument, making it possible to register a dynamic property from within a test's
+ * {@code ApplicationContext}. For example, a {@code @Bean} method can register
+ * a property whose value is dynamically sourced from the bean that the method
+ * returns. Note that such a {@code @Bean} method can optionally be annotated
+ * with {@code @DynamicPropertySource} to enforce eager initialization of the
+ * bean within the context, thereby ensuring that any dynamic properties sourced
+ * from that bean are available to other singleton beans within the context.
+ * See {@link DynamicPropertySource @DynamicPropertySource} for an example.
*
* @author Phillip Webb
* @author Sam Brannen
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 c21bb6b05e9..a491abe3949 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
@@ -23,29 +23,43 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
- * {@code @DynamicPropertySource} is an annotation that can be applied to methods
- * in integration test classes that need to add properties with dynamic values to
- * the {@code Environment}'s set of {@code PropertySources}.
+ * {@code @DynamicPropertySource} is an annotation that can be applied to static
+ * methods in integration test classes or to {@code @Bean} methods in test
+ * {@code @Configuration} classes in order to add properties with dynamic values
+ * to the {@code Environment}'s set of {@code PropertySources}.
*
*
This annotation and its supporting infrastructure were originally designed
* to allow properties from
* Testcontainers based tests to be
- * exposed easily to Spring integration tests. However, this feature may also be
- * used with any form of external resource whose lifecycle is maintained outside
+ * exposed easily to Spring integration tests. However, this feature may be used
+ * with any form of external resource whose lifecycle is managed outside the
+ * test's {@code ApplicationContext} or with beans whose lifecycle is managed by
* the test's {@code ApplicationContext}.
*
- *
Methods annotated with {@code @DynamicPropertySource} must be {@code static}
- * and must have a single {@link DynamicPropertyRegistry} argument which is used
- * to add name-value pairs to the {@code Environment}'s set of
- * {@code PropertySources}. Values are dynamic and provided via a
- * {@link java.util.function.Supplier} which is only invoked when the property
- * is resolved. Typically, method references are used to supply values, as in the
- * example below.
+ *
{@code @DynamicPropertySource}-annotated methods use a
+ * {@code DynamicPropertyRegistry} to add name-value pairs to the
+ * {@code Environment}'s set of {@code PropertySources}. Values are dynamic and
+ * provided via a {@link java.util.function.Supplier} which is only invoked when
+ * the property is resolved. Typically, method references are used to supply values,
+ * as in the example below.
*
- *
As of Spring Framework 5.3.2, dynamic properties from methods annotated with
- * {@code @DynamicPropertySource} will be inherited from enclosing test
- * classes, analogous to inheritance from superclasses and interfaces. See
- * {@link NestedTestConfiguration @NestedTestConfiguration} for details.
+ *
Methods in integration test classes that are annotated with
+ * {@code @DynamicPropertySource} must be {@code static} and must accept a single
+ * {@link DynamicPropertyRegistry} argument.
+ *
+ *
{@code @Bean} methods annotated with {@code @DynamicPropertySource} may
+ * either accept an argument of type {@code DynamicPropertyRegistry} or access a
+ * {@code DynamicPropertyRegistry} instance autowired into their enclosing
+ * {@code @Configuration} class. Note, however, that {@code @Bean} methods which
+ * interact with a {@code DynamicPropertyRegistry} are not required to be annotated
+ * with {@code @DynamicPropertySource} unless they need to enforce eager
+ * initialization of the bean within the context.
+ * See {@link DynamicPropertyRegistry} for details.
+ *
+ *
Dynamic properties from methods annotated with {@code @DynamicPropertySource}
+ * will be inherited from enclosing test classes, analogous to inheritance
+ * from superclasses and interfaces.
+ * See {@link NestedTestConfiguration @NestedTestConfiguration} for details.
*
*
NOTE: if you use {@code @DynamicPropertySource} in a base
* class and discover that tests in subclasses fail because the dynamic properties
@@ -64,7 +78,13 @@ import java.lang.annotation.Target;
* override properties loaded via {@code @TestPropertySource}, system property
* sources, and application property sources.
*
- *
Example
+ * Examples
+ *
+ * The following example demonstrates how to use {@code @DynamicPropertySource}
+ * in an integration test class. Beans in the {@code ApplicationContext} can
+ * access the {@code redis.host} and {@code redis.port} properties which are
+ * dynamically retrieved from the Redis container.
+ *
*
* @SpringJUnitConfig(...)
* @Testcontainers
@@ -81,7 +101,24 @@ import java.lang.annotation.Target;
* registry.add("redis.host", redis::getHost);
* registry.add("redis.port", redis::getFirstMappedPort);
* }
+ * }
*
+ * The following example demonstrates how to use {@code @DynamicPropertySource}
+ * with a {@code @Bean} method. Beans in the {@code ApplicationContext} can
+ * access the {@code api.url} property which is dynamically retrieved from the
+ * {@code ApiServer} bean.
+ *
+ *
+ * @Configuration
+ * class TestConfig {
+ *
+ * @Bean
+ * @DynamicPropertySource
+ * ApiServer apiServer(DynamicPropertyRegistry registry) {
+ * ApiServer apiServer = new ApiServer();
+ * registry.add("api.url", apiServer::getUrl);
+ * return apiServer;
+ * }
* }
*
* @author Phillip Webb
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/DynamicPropertiesContextCustomizer.java b/spring-test/src/main/java/org/springframework/test/context/support/DynamicPropertiesContextCustomizer.java
index 266782314aa..91604615458 100644
--- a/spring-test/src/main/java/org/springframework/test/context/support/DynamicPropertiesContextCustomizer.java
+++ b/spring-test/src/main/java/org/springframework/test/context/support/DynamicPropertiesContextCustomizer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2023 the original author or authors.
+ * Copyright 2002-2024 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.
@@ -18,14 +18,15 @@ package org.springframework.test.context.support;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.Map;
import java.util.Set;
-import java.util.function.Supplier;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ConfigurableApplicationContext;
-import org.springframework.core.env.MutablePropertySources;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.PropertySource;
import org.springframework.lang.Nullable;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.DynamicPropertyRegistry;
@@ -35,8 +36,10 @@ import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/**
- * {@link ContextCustomizer} to support
- * {@link DynamicPropertySource @DynamicPropertySource} methods.
+ * {@link ContextCustomizer} which supports
+ * {@link DynamicPropertySource @DynamicPropertySource} methods and registers a
+ * {@link DynamicPropertyRegistry} as a singleton bean in the container for use
+ * in {@code @Configuration} classes and {@code @Bean} methods.
*
* @author Phillip Webb
* @author Sam Brannen
@@ -45,7 +48,12 @@ import org.springframework.util.ReflectionUtils;
*/
class DynamicPropertiesContextCustomizer implements ContextCustomizer {
- private static final String PROPERTY_SOURCE_NAME = "Dynamic Test Properties";
+ private static final String DYNAMIC_PROPERTY_REGISTRY_BEAN_NAME =
+ DynamicPropertiesContextCustomizer.class.getName() + ".dynamicPropertyRegistry";
+
+ private static final String DYNAMIC_PROPERTY_SOURCE_BEAN_INITIALIZER_BEAN_NAME =
+ DynamicPropertiesContextCustomizer.class.getName() + "dynamicPropertySourceBeanInitializer";
+
private final Set methods;
@@ -61,27 +69,32 @@ class DynamicPropertiesContextCustomizer implements ContextCustomizer {
() -> "@DynamicPropertySource method '" + method.getName() + "' must be static");
Class>[] types = method.getParameterTypes();
Assert.state(types.length == 1 && types[0] == DynamicPropertyRegistry.class,
- () -> "@DynamicPropertySource method '" + method.getName() + "' must accept a single DynamicPropertyRegistry argument");
+ () -> "@DynamicPropertySource method '" + method.getName() +
+ "' must accept a single DynamicPropertyRegistry argument");
}
@Override
public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
- MutablePropertySources sources = context.getEnvironment().getPropertySources();
- sources.addFirst(new DynamicValuesPropertySource(PROPERTY_SOURCE_NAME, buildDynamicPropertiesMap()));
- }
+ DynamicValuesPropertySource propertySource = getOrAdd(context.getEnvironment());
+
+ if (!context.containsBean(DYNAMIC_PROPERTY_REGISTRY_BEAN_NAME)) {
+ ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
+ beanFactory.registerSingleton(DYNAMIC_PROPERTY_REGISTRY_BEAN_NAME, propertySource.dynamicPropertyRegistry);
+ }
+
+ if (!context.containsBean(DYNAMIC_PROPERTY_SOURCE_BEAN_INITIALIZER_BEAN_NAME)) {
+ if (!(context.getBeanFactory() instanceof BeanDefinitionRegistry registry)) {
+ throw new IllegalStateException("BeanFactory must be a BeanDefinitionRegistry");
+ }
+ BeanDefinition beanDefinition = new RootBeanDefinition(DynamicPropertySourceBeanInitializer.class);
+ beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ registry.registerBeanDefinition(DYNAMIC_PROPERTY_SOURCE_BEAN_INITIALIZER_BEAN_NAME, beanDefinition);
+ }
- private Map> buildDynamicPropertiesMap() {
- Map> map = new LinkedHashMap<>();
- DynamicPropertyRegistry dynamicPropertyRegistry = (name, valueSupplier) -> {
- Assert.hasText(name, "'name' must not be null or blank");
- Assert.notNull(valueSupplier, "'valueSupplier' must not be null");
- map.put(name, valueSupplier);
- };
this.methods.forEach(method -> {
ReflectionUtils.makeAccessible(method);
- ReflectionUtils.invokeMethod(method, null, dynamicPropertyRegistry);
+ ReflectionUtils.invokeMethod(method, null, propertySource.dynamicPropertyRegistry);
});
- return Collections.unmodifiableMap(map);
}
Set getMethods() {
@@ -100,4 +113,17 @@ class DynamicPropertiesContextCustomizer implements ContextCustomizer {
return this.methods.hashCode();
}
+
+ private static DynamicValuesPropertySource getOrAdd(ConfigurableEnvironment environment) {
+ PropertySource> propertySource = environment.getPropertySources()
+ .get(DynamicValuesPropertySource.PROPERTY_SOURCE_NAME);
+ if (propertySource == null) {
+ environment.getPropertySources().addFirst(new DynamicValuesPropertySource());
+ return getOrAdd(environment);
+ }
+ Assert.state(propertySource instanceof DynamicValuesPropertySource,
+ "Incorrect DynamicValuesPropertySource type registered");
+ return (DynamicValuesPropertySource) propertySource;
+ }
+
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/DynamicPropertiesContextCustomizerFactory.java b/spring-test/src/main/java/org/springframework/test/context/support/DynamicPropertiesContextCustomizerFactory.java
index 7f9e3da7798..f25a7335eef 100644
--- a/spring-test/src/main/java/org/springframework/test/context/support/DynamicPropertiesContextCustomizerFactory.java
+++ b/spring-test/src/main/java/org/springframework/test/context/support/DynamicPropertiesContextCustomizerFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2023 the original author or authors.
+ * Copyright 2002-2024 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.
@@ -17,6 +17,7 @@
package org.springframework.test.context.support;
import java.lang.reflect.Method;
+import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@@ -26,12 +27,15 @@ import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.lang.Nullable;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizerFactory;
+import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.context.TestContextAnnotationUtils;
/**
- * {@link ContextCustomizerFactory} to support
- * {@link DynamicPropertySource @DynamicPropertySource} methods.
+ * {@link ContextCustomizerFactory} which supports
+ * {@link DynamicPropertySource @DynamicPropertySource} methods and the
+ * registration of a {@link DynamicPropertyRegistry} as a singleton bean in the
+ * container for use in {@code @Configuration} classes and {@code @Bean} methods.
*
* @author Phillip Webb
* @author Sam Brannen
@@ -49,7 +53,7 @@ class DynamicPropertiesContextCustomizerFactory implements ContextCustomizerFact
Set methods = new LinkedHashSet<>();
findMethods(testClass, methods);
if (methods.isEmpty()) {
- return null;
+ methods = Collections.emptySet();
}
return new DynamicPropertiesContextCustomizer(methods);
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/DynamicPropertySourceBeanInitializer.java b/spring-test/src/main/java/org/springframework/test/context/support/DynamicPropertySourceBeanInitializer.java
new file mode 100644
index 00000000000..ace5ea8ad8e
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/support/DynamicPropertySourceBeanInitializer.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2002-2024 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.support;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.ListableBeanFactory;
+import org.springframework.context.weaving.LoadTimeWeaverAware;
+import org.springframework.instrument.classloading.LoadTimeWeaver;
+import org.springframework.lang.Nullable;
+import org.springframework.test.context.DynamicPropertySource;
+
+/**
+ * Internal component which eagerly initializes beans created by {@code @Bean}
+ * factory methods annotated with {@link DynamicPropertySource @DynamicPropertySource}.
+ *
+ * This class implements {@link LoadTimeWeaverAware} since doing so is
+ * currently the only way to have a component eagerly initialized before the
+ * {@code ConfigurableListableBeanFactory.preInstantiateSingletons()} phase.
+ *
+ * @author Sam Brannen
+ * @since 6.2
+ */
+class DynamicPropertySourceBeanInitializer implements BeanFactoryAware, InitializingBean, LoadTimeWeaverAware {
+
+ private static final Log logger = LogFactory.getLog(DynamicPropertySourceBeanInitializer.class);
+
+ @Nullable
+ private BeanFactory beanFactory;
+
+
+ @Override
+ public void setBeanFactory(BeanFactory beanFactory) {
+ this.beanFactory = beanFactory;
+ }
+
+ @Override
+ public void afterPropertiesSet() {
+ if (!(this.beanFactory instanceof ListableBeanFactory lbf)) {
+ throw new IllegalStateException("BeanFactory must be set and must be a ListableBeanFactory");
+ }
+ for (String name : lbf.getBeanNamesForAnnotation(DynamicPropertySource.class)) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Eagerly initializing @DynamicPropertySource bean '%s'".formatted(name));
+ }
+ this.beanFactory.getBean(name);
+ }
+ }
+
+ @Override
+ public void setLoadTimeWeaver(LoadTimeWeaver loadTimeWeaver) {
+ // no-op
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/DynamicValuesPropertySource.java b/spring-test/src/main/java/org/springframework/test/context/support/DynamicValuesPropertySource.java
index b87662215f4..8205a6c5568 100644
--- a/spring-test/src/main/java/org/springframework/test/context/support/DynamicValuesPropertySource.java
+++ b/spring-test/src/main/java/org/springframework/test/context/support/DynamicValuesPropertySource.java
@@ -16,11 +16,15 @@
package org.springframework.test.context.support;
+import java.util.Collections;
+import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Supplier;
import org.springframework.core.env.MapPropertySource;
import org.springframework.lang.Nullable;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.util.Assert;
import org.springframework.util.function.SupplierUtils;
/**
@@ -33,9 +37,22 @@ import org.springframework.util.function.SupplierUtils;
*/
class DynamicValuesPropertySource extends MapPropertySource {
- @SuppressWarnings({"rawtypes", "unchecked"})
- DynamicValuesPropertySource(String name, Map> valueSuppliers) {
- super(name, (Map) valueSuppliers);
+ static final String PROPERTY_SOURCE_NAME = "Dynamic Test Properties";
+
+ final DynamicPropertyRegistry dynamicPropertyRegistry;
+
+
+ DynamicValuesPropertySource() {
+ this(Collections.synchronizedMap(new LinkedHashMap<>()));
+ }
+
+ DynamicValuesPropertySource(Map> valueSuppliers) {
+ super(PROPERTY_SOURCE_NAME, Collections.unmodifiableMap(valueSuppliers));
+ this.dynamicPropertyRegistry = (name, valueSupplier) -> {
+ Assert.hasText(name, "'name' must not be null or blank");
+ Assert.notNull(valueSupplier, "'valueSupplier' must not be null");
+ valueSuppliers.put(name, valueSupplier);
+ };
}
@Override
diff --git a/spring-test/src/test/java/org/springframework/test/context/DynamicPropertyRegistryIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/DynamicPropertyRegistryIntegrationTests.java
new file mode 100644
index 00000000000..c659f6f495b
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/DynamicPropertyRegistryIntegrationTests.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2002-2024 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;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.Environment;
+import org.springframework.core.env.MutablePropertySources;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Integration tests for {@link DynamicPropertyRegistry} bean support.
+ *
+ * @author Sam Brannen
+ * @since 6.2
+ * @see DynamicPropertySourceIntegrationTests
+ */
+@SpringJUnitConfig
+@TestPropertySource(properties = "api.url: https://example.com/test")
+class DynamicPropertyRegistryIntegrationTests {
+
+ private static final String API_URL = "api.url";
+
+
+ @Test
+ void dynamicPropertySourceOverridesTestPropertySource(@Autowired ConfigurableEnvironment env) {
+ assertApiUrlIsDynamic(env.getProperty(API_URL));
+
+ MutablePropertySources propertySources = env.getPropertySources();
+ assertThat(propertySources.size()).isGreaterThanOrEqualTo(4);
+ assertThat(propertySources.contains("Inlined Test Properties")).isTrue();
+ assertThat(propertySources.contains("Dynamic Test Properties")).isTrue();
+ assertThat(propertySources.get("Inlined Test Properties").getProperty(API_URL)).isEqualTo("https://example.com/test");
+ assertThat(propertySources.get("Dynamic Test Properties").getProperty(API_URL)).isEqualTo("https://example.com/dynamic");
+ }
+
+ @Test
+ void testReceivesDynamicProperty(@Value("${api.url}") String apiUrl) {
+ assertApiUrlIsDynamic(apiUrl);
+ }
+
+ @Test
+ void environmentInjectedServiceCanRetrieveDynamicProperty(@Autowired EnvironmentInjectedService service) {
+ assertApiUrlIsDynamic(service);
+ }
+
+ @Test
+ void constructorInjectedServiceReceivesDynamicProperty(@Autowired ConstructorInjectedService service) {
+ assertApiUrlIsDynamic(service);
+ }
+
+ @Test
+ void setterInjectedServiceReceivesDynamicProperty(@Autowired SetterInjectedService service) {
+ assertApiUrlIsDynamic(service);
+ }
+
+
+ private static void assertApiUrlIsDynamic(ApiUrlClient service) {
+ assertApiUrlIsDynamic(service.getApiUrl());
+ }
+
+ private static void assertApiUrlIsDynamic(String apiUrl) {
+ assertThat(apiUrl).isEqualTo("https://example.com/dynamic");
+ }
+
+
+ @Configuration
+ @Import({ EnvironmentInjectedService.class, ConstructorInjectedService.class, SetterInjectedService.class })
+ static class Config {
+
+ // Annotating this @Bean method with @DynamicPropertySource ensures that
+ // this bean will be instantiated before any other singleton beans in the
+ // context which further ensures that the dynamic "api.url" property is
+ // available to all standard singleton beans.
+ @Bean
+ @DynamicPropertySource
+ ApiServer apiServer(DynamicPropertyRegistry registry) {
+ ApiServer apiServer = new ApiServer();
+ registry.add(API_URL, apiServer::getUrl);
+ return apiServer;
+ }
+
+ }
+
+ interface ApiUrlClient {
+
+ String getApiUrl();
+ }
+
+ static class EnvironmentInjectedService implements ApiUrlClient {
+
+ private final Environment env;
+
+
+ EnvironmentInjectedService(Environment env) {
+ this.env = env;
+ }
+
+ @Override
+ public String getApiUrl() {
+ return this.env.getProperty(API_URL);
+ }
+ }
+
+ static class ConstructorInjectedService implements ApiUrlClient {
+
+ private final String apiUrl;
+
+
+ ConstructorInjectedService(@Value("${api.url}") String apiUrl) {
+ this.apiUrl = apiUrl;
+ }
+
+ @Override
+ public String getApiUrl() {
+ return this.apiUrl;
+ }
+ }
+
+ static class SetterInjectedService implements ApiUrlClient {
+
+ private String apiUrl;
+
+
+ @Autowired
+ void setApiUrl(@Value("${api.url}") String apiUrl) {
+ this.apiUrl = apiUrl;
+ }
+
+ @Override
+ public String getApiUrl() {
+ return this.apiUrl;
+ }
+ }
+
+ static class ApiServer {
+
+ String getUrl() {
+ return "https://example.com/dynamic";
+ }
+ }
+
+}
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 550de54679f..73a86f9dcb5 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
@@ -37,6 +37,7 @@ import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
*
* @author Phillip Webb
* @author Sam Brannen
+ * @see DynamicPropertyRegistryIntegrationTests
*/
@SpringJUnitConfig
@TestPropertySource(properties = "test.container.ip: test")
diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/AbstractAotTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/AbstractAotTests.java
index a18710b975b..99e746d8add 100644
--- a/spring-test/src/test/java/org/springframework/test/context/aot/AbstractAotTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/aot/AbstractAotTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2023 the original author or authors.
+ * Copyright 2002-2024 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.
@@ -40,6 +40,7 @@ abstract class AbstractAotTests {
"org/springframework/test/context/aot/samples/basic/BasicSpringJupiterImportedConfigTests__TestContext001_BeanDefinitions.java",
"org/springframework/test/context/aot/samples/basic/BasicSpringJupiterImportedConfigTests__TestContext001_BeanFactoryRegistrations.java",
"org/springframework/test/context/aot/samples/basic/BasicTestConfiguration__TestContext001_BeanDefinitions.java",
+ "org/springframework/test/context/support/DynamicPropertySourceBeanInitializer__TestContext001_BeanDefinitions.java",
// BasicSpringJupiterSharedConfigTests
"org/springframework/context/event/DefaultEventListenerFactory__TestContext002_BeanDefinitions.java",
"org/springframework/context/event/EventListenerMethodProcessor__TestContext002_BeanDefinitions.java",
@@ -50,6 +51,7 @@ abstract class AbstractAotTests {
"org/springframework/test/context/aot/samples/basic/BasicTestConfiguration__TestContext002_BeanDefinitions.java",
"org/springframework/test/context/aot/samples/management/ManagementConfiguration__TestContext002_BeanDefinitions.java",
"org/springframework/test/context/aot/samples/management/ManagementMessageService__TestContext002_ManagementBeanDefinitions.java",
+ "org/springframework/test/context/support/DynamicPropertySourceBeanInitializer__TestContext002_BeanDefinitions.java",
// BasicSpringJupiterTests -- not generated b/c already generated for BasicSpringJupiterSharedConfigTests.
// BasicSpringJupiterTests.NestedTests
"org/springframework/context/event/DefaultEventListenerFactory__TestContext003_BeanDefinitions.java",
@@ -61,24 +63,28 @@ abstract class AbstractAotTests {
"org/springframework/test/context/aot/samples/basic/BasicTestConfiguration__TestContext003_BeanDefinitions.java",
"org/springframework/test/context/aot/samples/management/ManagementConfiguration__TestContext003_BeanDefinitions.java",
"org/springframework/test/context/aot/samples/management/ManagementMessageService__TestContext003_ManagementBeanDefinitions.java",
+ "org/springframework/test/context/support/DynamicPropertySourceBeanInitializer__TestContext003_BeanDefinitions.java",
// BasicSpringTestNGTests
"org/springframework/context/event/DefaultEventListenerFactory__TestContext004_BeanDefinitions.java",
"org/springframework/context/event/EventListenerMethodProcessor__TestContext004_BeanDefinitions.java",
"org/springframework/test/context/aot/samples/basic/BasicSpringTestNGTests__TestContext004_ApplicationContextInitializer.java",
"org/springframework/test/context/aot/samples/basic/BasicSpringTestNGTests__TestContext004_BeanFactoryRegistrations.java",
"org/springframework/test/context/aot/samples/basic/BasicTestConfiguration__TestContext004_BeanDefinitions.java",
+ "org/springframework/test/context/support/DynamicPropertySourceBeanInitializer__TestContext004_BeanDefinitions.java",
// BasicSpringVintageTests
"org/springframework/context/event/DefaultEventListenerFactory__TestContext005_BeanDefinitions.java",
"org/springframework/context/event/EventListenerMethodProcessor__TestContext005_BeanDefinitions.java",
"org/springframework/test/context/aot/samples/basic/BasicSpringVintageTests__TestContext005_ApplicationContextInitializer.java",
"org/springframework/test/context/aot/samples/basic/BasicSpringVintageTests__TestContext005_BeanFactoryRegistrations.java",
"org/springframework/test/context/aot/samples/basic/BasicTestConfiguration__TestContext005_BeanDefinitions.java",
+ "org/springframework/test/context/support/DynamicPropertySourceBeanInitializer__TestContext005_BeanDefinitions.java",
// DisabledInAotRuntimeMethodLevelTests
"org/springframework/context/event/DefaultEventListenerFactory__TestContext006_BeanDefinitions.java",
"org/springframework/context/event/EventListenerMethodProcessor__TestContext006_BeanDefinitions.java",
"org/springframework/test/context/aot/samples/basic/DisabledInAotRuntimeMethodLevelTests__TestContext006_ApplicationContextInitializer.java",
"org/springframework/test/context/aot/samples/basic/DisabledInAotRuntimeMethodLevelTests__TestContext006_BeanDefinitions.java",
- "org/springframework/test/context/aot/samples/basic/DisabledInAotRuntimeMethodLevelTests__TestContext006_BeanFactoryRegistrations.java"
+ "org/springframework/test/context/aot/samples/basic/DisabledInAotRuntimeMethodLevelTests__TestContext006_BeanFactoryRegistrations.java",
+ "org/springframework/test/context/support/DynamicPropertySourceBeanInitializer__TestContext006_BeanDefinitions.java"
};
Stream> scan() {
diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorIntegrationTests.java
index 0fdbe04b1ef..d98478befd4 100644
--- a/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorIntegrationTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorIntegrationTests.java
@@ -395,6 +395,7 @@ class TestContextAotGeneratorIntegrationTests extends AbstractAotTests {
"org/springframework/test/context/aot/samples/basic/BasicTestConfiguration__TestContext001_BeanDefinitions.java",
"org/springframework/test/context/aot/samples/management/ManagementConfiguration__TestContext001_BeanDefinitions.java",
"org/springframework/test/context/aot/samples/management/ManagementMessageService__TestContext001_ManagementBeanDefinitions.java",
+ "org/springframework/test/context/support/DynamicPropertySourceBeanInitializer__TestContext001_BeanDefinitions.java",
// BasicSpringJupiterTests -- not generated b/c already generated for BasicSpringJupiterSharedConfigTests.
// BasicSpringJupiterTests.NestedTests
"org/springframework/context/event/DefaultEventListenerFactory__TestContext002_BeanDefinitions.java",
@@ -406,24 +407,28 @@ class TestContextAotGeneratorIntegrationTests extends AbstractAotTests {
"org/springframework/test/context/aot/samples/basic/BasicTestConfiguration__TestContext002_BeanDefinitions.java",
"org/springframework/test/context/aot/samples/management/ManagementConfiguration__TestContext002_BeanDefinitions.java",
"org/springframework/test/context/aot/samples/management/ManagementMessageService__TestContext002_ManagementBeanDefinitions.java",
+ "org/springframework/test/context/support/DynamicPropertySourceBeanInitializer__TestContext002_BeanDefinitions.java",
// BasicSpringTestNGTests
"org/springframework/context/event/DefaultEventListenerFactory__TestContext003_BeanDefinitions.java",
"org/springframework/context/event/EventListenerMethodProcessor__TestContext003_BeanDefinitions.java",
"org/springframework/test/context/aot/samples/basic/BasicSpringTestNGTests__TestContext003_ApplicationContextInitializer.java",
"org/springframework/test/context/aot/samples/basic/BasicSpringTestNGTests__TestContext003_BeanFactoryRegistrations.java",
"org/springframework/test/context/aot/samples/basic/BasicTestConfiguration__TestContext003_BeanDefinitions.java",
+ "org/springframework/test/context/support/DynamicPropertySourceBeanInitializer__TestContext003_BeanDefinitions.java",
// BasicSpringVintageTests
"org/springframework/context/event/DefaultEventListenerFactory__TestContext004_BeanDefinitions.java",
"org/springframework/context/event/EventListenerMethodProcessor__TestContext004_BeanDefinitions.java",
"org/springframework/test/context/aot/samples/basic/BasicSpringVintageTests__TestContext004_ApplicationContextInitializer.java",
"org/springframework/test/context/aot/samples/basic/BasicSpringVintageTests__TestContext004_BeanFactoryRegistrations.java",
"org/springframework/test/context/aot/samples/basic/BasicTestConfiguration__TestContext004_BeanDefinitions.java",
+ "org/springframework/test/context/support/DynamicPropertySourceBeanInitializer__TestContext004_BeanDefinitions.java",
// SqlScriptsSpringJupiterTests
"org/springframework/context/event/DefaultEventListenerFactory__TestContext005_BeanDefinitions.java",
"org/springframework/context/event/EventListenerMethodProcessor__TestContext005_BeanDefinitions.java",
"org/springframework/test/context/aot/samples/jdbc/SqlScriptsSpringJupiterTests__TestContext005_ApplicationContextInitializer.java",
"org/springframework/test/context/aot/samples/jdbc/SqlScriptsSpringJupiterTests__TestContext005_BeanFactoryRegistrations.java",
"org/springframework/test/context/jdbc/EmptyDatabaseConfig__TestContext005_BeanDefinitions.java",
+ "org/springframework/test/context/support/DynamicPropertySourceBeanInitializer__TestContext005_BeanDefinitions.java",
// WebSpringJupiterTests
"org/springframework/context/event/DefaultEventListenerFactory__TestContext006_BeanDefinitions.java",
"org/springframework/context/event/EventListenerMethodProcessor__TestContext006_BeanDefinitions.java",
@@ -432,12 +437,14 @@ class TestContextAotGeneratorIntegrationTests extends AbstractAotTests {
"org/springframework/test/context/aot/samples/web/WebTestConfiguration__TestContext006_BeanDefinitions.java",
"org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration__TestContext006_Autowiring.java",
"org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration__TestContext006_BeanDefinitions.java",
+ "org/springframework/test/context/support/DynamicPropertySourceBeanInitializer__TestContext006_BeanDefinitions.java",
// XmlSpringJupiterTests
"org/springframework/context/event/DefaultEventListenerFactory__TestContext007_BeanDefinitions.java",
"org/springframework/context/event/EventListenerMethodProcessor__TestContext007_BeanDefinitions.java",
"org/springframework/test/context/aot/samples/common/DefaultMessageService__TestContext007_BeanDefinitions.java",
"org/springframework/test/context/aot/samples/xml/XmlSpringJupiterTests__TestContext007_ApplicationContextInitializer.java",
- "org/springframework/test/context/aot/samples/xml/XmlSpringJupiterTests__TestContext007_BeanFactoryRegistrations.java"
+ "org/springframework/test/context/aot/samples/xml/XmlSpringJupiterTests__TestContext007_BeanFactoryRegistrations.java",
+ "org/springframework/test/context/support/DynamicPropertySourceBeanInitializer__TestContext007_BeanDefinitions.java",
};
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsMergedConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsMergedConfigTests.java
index 5dd24e7ebef..6345184bbbe 100644
--- a/spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsMergedConfigTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsMergedConfigTests.java
@@ -36,7 +36,6 @@ import org.springframework.test.context.web.WebDelegatingSmartContextLoader;
import org.springframework.test.context.web.WebMergedContextConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link BootstrapTestUtils} involving {@link MergedContextConfiguration}.
@@ -59,10 +58,14 @@ class BootstrapTestUtilsMergedConfigTests extends AbstractContextConfigurationUt
*/
@Test
void buildMergedConfigWithContextConfigurationWithoutLocationsClassesOrInitializers() {
- assertThatIllegalStateException().isThrownBy(() ->
- buildMergedContextConfiguration(MissingContextAttributesTestCase.class))
- .withMessageStartingWith("DelegatingSmartContextLoader was unable to detect defaults, "
- + "and no ApplicationContextInitializers or ContextCustomizers were declared for context configuration attributes");
+ Class> testClass = MissingContextAttributesTestCase.class;
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass);
+
+ assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, DelegatingSmartContextLoader.class);
+ assertThat(mergedConfig.getContextCustomizers())
+ .map(Object::getClass)
+ .map(Class::getSimpleName)
+ .containsOnly("DynamicPropertiesContextCustomizer");
}
@Test
diff --git a/spring-test/src/test/java/org/springframework/test/context/support/DynamicPropertiesContextCustomizerFactoryTests.java b/spring-test/src/test/java/org/springframework/test/context/support/DynamicPropertiesContextCustomizerFactoryTests.java
index 4f58a1b65b1..42211185917 100644
--- a/spring-test/src/test/java/org/springframework/test/context/support/DynamicPropertiesContextCustomizerFactoryTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/support/DynamicPropertiesContextCustomizerFactoryTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2024 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.
@@ -40,10 +40,11 @@ class DynamicPropertiesContextCustomizerFactoryTests {
private final List configAttributes = Collections.emptyList();
@Test
- void createContextCustomizerWhenNoAnnotatedMethodsReturnsNull() {
+ void createContextCustomizerWhenNoAnnotatedMethodsReturnsCustomizerWithEmptyMethods() {
DynamicPropertiesContextCustomizer customizer = this.factory.createContextCustomizer(
NoDynamicPropertySource.class, this.configAttributes);
- assertThat(customizer).isNull();
+ assertThat(customizer).isNotNull();
+ assertThat(customizer.getMethods()).isEmpty();
}
@Test
diff --git a/spring-test/src/test/java/org/springframework/test/context/support/DynamicValuesPropertySourceTests.java b/spring-test/src/test/java/org/springframework/test/context/support/DynamicValuesPropertySourceTests.java
index 065486b2a27..7f75fe927eb 100644
--- a/spring-test/src/test/java/org/springframework/test/context/support/DynamicValuesPropertySourceTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/support/DynamicValuesPropertySourceTests.java
@@ -30,7 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
class DynamicValuesPropertySourceTests {
- private final DynamicValuesPropertySource source = new DynamicValuesPropertySource("test",
+ private final DynamicValuesPropertySource source = new DynamicValuesPropertySource(
Map.of("a", () -> "A", "b", () -> "B"));