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 518ce44cb88d..0474f9856bda 100644
--- a/spring-test/src/main/java/org/springframework/test/context/DynamicPropertySource.java
+++ b/spring-test/src/main/java/org/springframework/test/context/DynamicPropertySource.java
@@ -41,6 +41,11 @@
* 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.
+ *
*
NOTE: if you use {@code @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
diff --git a/spring-test/src/main/java/org/springframework/test/context/NestedTestConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/NestedTestConfiguration.java
index 48b4b91e9f63..f9d245f82d42 100644
--- a/spring-test/src/main/java/org/springframework/test/context/NestedTestConfiguration.java
+++ b/spring-test/src/main/java/org/springframework/test/context/NestedTestConfiguration.java
@@ -74,6 +74,7 @@
*
{@link org.springframework.test.context.web.WebAppConfiguration @WebAppConfiguration}
* {@link ActiveProfiles @ActiveProfiles}
* {@link TestPropertySource @TestPropertySource}
+ * {@link DynamicPropertySource @DynamicPropertySource}
* {@link org.springframework.test.annotation.DirtiesContext @DirtiesContext}
* {@link org.springframework.transaction.annotation.Transactional @Transactional}
* {@link org.springframework.test.annotation.Rollback @Rollback}
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 7760a43a5067..11779cc12339 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
@@ -17,6 +17,7 @@
package org.springframework.test.context.support;
import java.lang.reflect.Method;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@@ -26,12 +27,14 @@
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizerFactory;
import org.springframework.test.context.DynamicPropertySource;
+import org.springframework.test.context.TestContextAnnotationUtils;
/**
* {@link ContextCustomizerFactory} to support
* {@link DynamicPropertySource @DynamicPropertySource} methods.
*
* @author Phillip Webb
+ * @author Sam Brannen
* @since 5.2.5
* @see DynamicPropertiesContextCustomizer
*/
@@ -42,13 +45,21 @@ class DynamicPropertiesContextCustomizerFactory implements ContextCustomizerFact
public DynamicPropertiesContextCustomizer createContextCustomizer(Class> testClass,
List configAttributes) {
- Set methods = MethodIntrospector.selectMethods(testClass, this::isAnnotated);
+ Set methods = new LinkedHashSet<>();
+ findMethods(testClass, methods);
if (methods.isEmpty()) {
return null;
}
return new DynamicPropertiesContextCustomizer(methods);
}
+ private void findMethods(Class> testClass, Set methods) {
+ methods.addAll(MethodIntrospector.selectMethods(testClass, this::isAnnotated));
+ if (TestContextAnnotationUtils.searchEnclosingClass(testClass)) {
+ findMethods(testClass.getEnclosingClass(), methods);
+ }
+ }
+
private boolean isAnnotated(Method method) {
return MergedAnnotations.from(method).isPresent(DynamicPropertySource.class);
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/DynamicPropertySourceNestedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/DynamicPropertySourceNestedTests.java
new file mode 100644
index 000000000000..08113cee82cd
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/DynamicPropertySourceNestedTests.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2002-2020 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.junit.jupiter.nested;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+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.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+import org.springframework.test.context.NestedTestConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.OVERRIDE;
+
+/**
+ * Integration tests that verify support for {@code @Nested} test classes using
+ * {@link DynamicPropertySource @DynamicPropertySource} in conjunction with the
+ * {@link SpringExtension} in a JUnit Jupiter environment.
+ *
+ * @author Sam Brannen
+ * @since 5.3.2
+ */
+@SpringJUnitConfig
+class DynamicPropertySourceNestedTests {
+
+ private static final String TEST_CONTAINER_IP = "DynamicPropertySourceNestedTests.test.container.ip";
+
+ private static final String TEST_CONTAINER_PORT = "DynamicPropertySourceNestedTests.test.container.port";
+
+ static DemoContainer container = new DemoContainer();
+
+ @DynamicPropertySource
+ static void containerProperties(DynamicPropertyRegistry registry) {
+ registry.add(TEST_CONTAINER_IP, container::getIpAddress);
+ registry.add(TEST_CONTAINER_PORT, container::getPort);
+ }
+
+
+ @Test
+ @DisplayName("@Service has values injected from @DynamicPropertySource")
+ void serviceHasInjectedValues(@Autowired Service service) {
+ assertServiceHasInjectedValues(service);
+ }
+
+ private static void assertServiceHasInjectedValues(Service service) {
+ assertThat(service.getIp()).isEqualTo("127.0.0.1");
+ assertThat(service.getPort()).isEqualTo(4242);
+ }
+
+ @Nested
+ @NestedTestConfiguration(OVERRIDE)
+ @SpringJUnitConfig(Config.class)
+ class DynamicPropertySourceFromSuperclassTests extends DynamicPropertySourceSuperclass {
+
+ @Test
+ @DisplayName("@Service has values injected from @DynamicPropertySource in superclass")
+ void serviceHasInjectedValues(@Autowired Service service) {
+ assertServiceHasInjectedValues(service);
+ }
+ }
+
+ @Nested
+ @NestedTestConfiguration(OVERRIDE)
+ @SpringJUnitConfig(Config.class)
+ class DynamicPropertySourceFromInterfaceTests implements DynamicPropertySourceInterface {
+
+ @Test
+ @DisplayName("@Service has values injected from @DynamicPropertySource in interface")
+ void serviceHasInjectedValues(@Autowired Service service) {
+ assertServiceHasInjectedValues(service);
+ }
+ }
+
+ @Nested
+ @NestedTestConfiguration(OVERRIDE)
+ @SpringJUnitConfig(Config.class)
+ class OverriddenConfigTests {
+
+ @Test
+ @DisplayName("@Service does not have values injected from @DynamicPropertySource in enclosing class")
+ void serviceHasDefaultInjectedValues(@Autowired Service service) {
+ assertThat(service.getIp()).isEqualTo("10.0.0.1");
+ assertThat(service.getPort()).isEqualTo(-999);
+ }
+ }
+
+ @Nested
+ class DynamicPropertySourceFromEnclosingClassTests {
+
+ @Test
+ @DisplayName("@Service has values injected from @DynamicPropertySource in enclosing class")
+ void serviceHasInjectedValues(@Autowired Service service) {
+ assertServiceHasInjectedValues(service);
+ }
+
+ @Nested
+ class DoubleNestedDynamicPropertySourceFromEnclosingClassTests {
+
+ @Test
+ @DisplayName("@Service has values injected from @DynamicPropertySource in enclosing class")
+ void serviceHasInjectedValues(@Autowired Service service) {
+ assertServiceHasInjectedValues(service);
+ }
+ }
+ }
+
+
+ static abstract class DynamicPropertySourceSuperclass {
+
+ @DynamicPropertySource
+ static void containerProperties(DynamicPropertyRegistry registry) {
+ registry.add(TEST_CONTAINER_IP, container::getIpAddress);
+ registry.add(TEST_CONTAINER_PORT, container::getPort);
+ }
+ }
+
+ interface DynamicPropertySourceInterface {
+
+ @DynamicPropertySource
+ static void containerProperties(DynamicPropertyRegistry registry) {
+ registry.add(TEST_CONTAINER_IP, container::getIpAddress);
+ registry.add(TEST_CONTAINER_PORT, container::getPort);
+ }
+ }
+
+
+ @Configuration
+ @Import(Service.class)
+ static class Config {
+ }
+
+ static class Service {
+
+ private final String ip;
+
+ private final int port;
+
+
+ Service(@Value("${" + TEST_CONTAINER_IP + ":10.0.0.1}") String ip, @Value("${" + TEST_CONTAINER_PORT + ":-999}") int port) {
+ this.ip = ip;
+ this.port = port;
+ }
+
+ String getIp() {
+ return this.ip;
+ }
+
+ int getPort() {
+ return this.port;
+ }
+ }
+
+ static class DemoContainer {
+
+ String getIpAddress() {
+ return "127.0.0.1";
+ }
+
+ int getPort() {
+ return 4242;
+ }
+ }
+
+}
diff --git a/src/docs/asciidoc/testing.adoc b/src/docs/asciidoc/testing.adoc
index 9738ec87522b..f4dc2f87e14a 100644
--- a/src/docs/asciidoc/testing.adoc
+++ b/src/docs/asciidoc/testing.adoc
@@ -1877,6 +1877,7 @@ following annotations.
* <>
* <>
* <>
+* <>
* <>
* <>
* <>