Skip to content

Commit

Permalink
Discover @DynamicPropertySource methods on enclosing test classes
Browse files Browse the repository at this point in the history
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
  • Loading branch information
sbrannen committed Nov 15, 2020
1 parent 0b580d1 commit 0819a9f
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@
* is resolved. Typically, method references are used to supply values, as in the
* example below.
*
* <p>As of Spring Framework 5.3.2, dynamic properties from methods annotated with
* {@code @DynamicPropertySource} will be <em>inherited</em> from enclosing test
* classes, analogous to inheritance from superclasses and interfaces. See
* {@link NestedTestConfiguration @NestedTestConfiguration} for details.
*
* <p><strong>NOTE</strong>: 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
* <li>{@link org.springframework.test.context.web.WebAppConfiguration @WebAppConfiguration}</li>
* <li>{@link ActiveProfiles @ActiveProfiles}</li>
* <li>{@link TestPropertySource @TestPropertySource}</li>
* <li>{@link DynamicPropertySource @DynamicPropertySource}</li>
* <li>{@link org.springframework.test.annotation.DirtiesContext @DirtiesContext}</li>
* <li>{@link org.springframework.transaction.annotation.Transactional @Transactional}</li>
* <li>{@link org.springframework.test.annotation.Rollback @Rollback}</li>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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
*/
Expand All @@ -42,13 +45,21 @@ class DynamicPropertiesContextCustomizerFactory implements ContextCustomizerFact
public DynamicPropertiesContextCustomizer createContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {

Set<Method> methods = MethodIntrospector.selectMethods(testClass, this::isAnnotated);
Set<Method> methods = new LinkedHashSet<>();
findMethods(testClass, methods);
if (methods.isEmpty()) {
return null;
}
return new DynamicPropertiesContextCustomizer(methods);
}

private void findMethods(Class<?> testClass, Set<Method> 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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}

}
1 change: 1 addition & 0 deletions src/docs/asciidoc/testing.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1877,6 +1877,7 @@ following annotations.
* <<spring-testing-annotation-contexthierarchy>>
* <<spring-testing-annotation-activeprofiles>>
* <<spring-testing-annotation-testpropertysource>>
* <<spring-testing-annotation-dynamicpropertysource>>
* <<spring-testing-annotation-dirtiescontext>>
* <<spring-testing-annotation-testexecutionlisteners>>
* <<testcontext-tx,`@Transactional`>>
Expand Down

0 comments on commit 0819a9f

Please sign in to comment.