From 0819a9fcc9a1168521995e0bac7de5633a819780 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sun, 15 Nov 2020 20:59:15 +0100 Subject: [PATCH] Discover @DynamicPropertySource methods on enclosing test classes gh-19930 introduced support for finding class-level test configuration annotations on enclosing classes when using JUnit Jupiter @Nested test classes, but support for @DynamicPropertySource methods got overlooked since they are method-level annotations. This commit addresses this shortcoming by introducing full support for @NestedTestConfiguration semantics for @DynamicPropertySource methods on enclosing classes. Closes gh-26091 --- .../test/context/DynamicPropertySource.java | 5 + .../test/context/NestedTestConfiguration.java | 1 + ...micPropertiesContextCustomizerFactory.java | 13 +- .../DynamicPropertySourceNestedTests.java | 185 ++++++++++++++++++ src/docs/asciidoc/testing.adoc | 1 + 5 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/DynamicPropertySourceNestedTests.java 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. * <> * <> * <> +* <> * <> * <> * <>