From 6cdb34410bf14aacb08bf9d3b47d22daed821824 Mon Sep 17 00:00:00 2001
From: Sam Brannen <104798+sbrannen@users.noreply.github.com>
Date: Wed, 7 Feb 2024 16:24:35 +0100
Subject: [PATCH] Register DynamicPropertyRegistry as singleton bean in test
ApplicationContext
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
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
---
.../dynamic-property-sources.adoc | 113 +++++++++---
.../ctx-management/property-sources.adoc | 3 +-
.../test/context/DynamicPropertyRegistry.java | 24 ++-
.../test/context/DynamicPropertySource.java | 71 ++++++--
.../DynamicPropertiesContextCustomizer.java | 70 +++++---
...micPropertiesContextCustomizerFactory.java | 12 +-
.../DynamicPropertySourceBeanInitializer.java | 73 ++++++++
.../support/DynamicValuesPropertySource.java | 23 ++-
...namicPropertyRegistryIntegrationTests.java | 165 ++++++++++++++++++
...DynamicPropertySourceIntegrationTests.java | 1 +
.../test/context/aot/AbstractAotTests.java | 10 +-
...stContextAotGeneratorIntegrationTests.java | 9 +-
.../BootstrapTestUtilsMergedConfigTests.java | 13 +-
...opertiesContextCustomizerFactoryTests.java | 7 +-
.../DynamicValuesPropertySourceTests.java | 2 +-
15 files changed, 507 insertions(+), 89 deletions(-)
create mode 100644 spring-test/src/main/java/org/springframework/test/context/support/DynamicPropertySourceBeanInitializer.java
create mode 100644 spring-test/src/test/java/org/springframework/test/context/DynamicPropertyRegistryIntegrationTests.java
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"));