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 new file mode 100644 index 0000000000..2f521c4925 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/DynamicPropertyRegistry.java @@ -0,0 +1,40 @@ +/* + * 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; + +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. + * + * @author Phillip Webb + * @author Sam Brannen + * @since 5.2.5 + * @see DynamicPropertySource + */ +public interface DynamicPropertyRegistry { + + /** + * Add a {@link Supplier} for the given property name to this registry. + * @param name the name of the property for which the supplier should be added + * @param valueSupplier a supplier that will provide the property value on demand + */ + void add(String name, Supplier valueSupplier); + +} 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 new file mode 100644 index 0000000000..3ffa6f8885 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/DynamicPropertySource.java @@ -0,0 +1,75 @@ +/* + * 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; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Method-level annotation for integration tests that need 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 + * 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 Supplier} + * which is only invoked when the property is resolved. Typically, method references + * are used to supply values, as in the following example. + * + *

Example

+ * + *
+ * @SpringJUnitConfig(...)
+ * @Testcontainers
+ * class ExampleIntegrationTests {
+ *
+ *     @Container
+ *     static RedisContainer redis = new RedisContainer();
+ *
+ *     // ...
+ *
+ *     @DynamicPropertySource
+ *     static void redisProperties(DynamicPropertyRegistry registry) {
+ *         registry.add("redis.host", redis::getContainerIpAddress);
+ *         registry.add("redis.port", redis::getMappedPort);
+ *     }
+ *
+ * }
+ * + * @author Phillip Webb + * @author Sam Brannen + * @since 5.2.5 + * @see DynamicPropertyRegistry + * @see ContextConfiguration + * @see TestPropertySource + * @see org.springframework.core.env.PropertySource + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DynamicPropertySource { +} diff --git a/spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java b/spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java index f435f42a9d..84ebc26bad 100644 --- a/spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java +++ b/spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * 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. @@ -81,6 +81,7 @@ import org.springframework.core.annotation.AliasFor; * @author Sam Brannen * @since 4.1 * @see ContextConfiguration + * @see DynamicPropertySource * @see org.springframework.core.env.Environment * @see org.springframework.core.env.PropertySource * @see org.springframework.context.annotation.PropertySource 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 new file mode 100644 index 0000000000..fb0ec3f5a8 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/support/DynamicPropertiesContextCustomizer.java @@ -0,0 +1,111 @@ +/* + * 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.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.context.ConfigurableApplicationContext; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.lang.Nullable; +import org.springframework.test.context.ContextCustomizer; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.context.MergedContextConfiguration; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; + +/** + * {@link ContextCustomizer} to support + * {@link DynamicPropertySource @DynamicPropertySource} methods. + * + * @author Phillip Webb + * @author Sam Brannen + * @since 5.2.5 + * @see DynamicPropertiesContextCustomizerFactory + */ +class DynamicPropertiesContextCustomizer implements ContextCustomizer { + + private static final String PROPERTY_SOURCE_NAME = "Dynamic Test Properties"; + + + private final Set methods; + + + DynamicPropertiesContextCustomizer(Set methods) { + methods.forEach(this::assertValid); + this.methods = methods; + } + + + private void assertValid(Method method) { + Assert.state(Modifier.isStatic(method.getModifiers()), + () -> "@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"); + } + + @Override + public void customizeContext(ConfigurableApplicationContext context, + MergedContextConfiguration mergedConfig) { + + MutablePropertySources sources = context.getEnvironment().getPropertySources(); + sources.addFirst(new DynamicValuesPropertySource(PROPERTY_SOURCE_NAME, buildDynamicPropertiesMap())); + } + + @Nullable + 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); + }); + return Collections.unmodifiableMap(map); + } + + Set getMethods() { + return this.methods; + } + + @Override + public int hashCode() { + return this.methods.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + return this.methods.equals(((DynamicPropertiesContextCustomizer) obj).methods); + } + +} 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 new file mode 100644 index 0000000000..7760a43a50 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/support/DynamicPropertiesContextCustomizerFactory.java @@ -0,0 +1,56 @@ +/* + * 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.support; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Set; + +import org.springframework.core.MethodIntrospector; +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.DynamicPropertySource; + +/** + * {@link ContextCustomizerFactory} to support + * {@link DynamicPropertySource @DynamicPropertySource} methods. + * + * @author Phillip Webb + * @since 5.2.5 + * @see DynamicPropertiesContextCustomizer + */ +class DynamicPropertiesContextCustomizerFactory implements ContextCustomizerFactory { + + @Override + @Nullable + public DynamicPropertiesContextCustomizer createContextCustomizer(Class testClass, + List configAttributes) { + + Set methods = MethodIntrospector.selectMethods(testClass, this::isAnnotated); + if (methods.isEmpty()) { + return null; + } + return new DynamicPropertiesContextCustomizer(methods); + } + + private boolean isAnnotated(Method method) { + return MergedAnnotations.from(method).isPresent(DynamicPropertySource.class); + } + +} 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 new file mode 100644 index 0000000000..a8e623ea3f --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/support/DynamicValuesPropertySource.java @@ -0,0 +1,56 @@ +/* + * 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.support; + +import java.util.Map; +import java.util.function.Supplier; + +import org.springframework.core.env.EnumerablePropertySource; +import org.springframework.util.StringUtils; + +/** + * {@link EnumerablePropertySource} backed by a map with dynamically supplied + * values. + * + * @author Phillip Webb + * @author Sam Brannen + * @since 5.2.5 + */ +class DynamicValuesPropertySource extends EnumerablePropertySource>> { + + DynamicValuesPropertySource(String name, Map> valueSuppliers) { + super(name, valueSuppliers); + } + + + @Override + public Object getProperty(String name) { + Supplier valueSupplier = this.source.get(name); + return (valueSupplier != null ? valueSupplier.get() : null); + } + + @Override + public boolean containsProperty(String name) { + return this.source.containsKey(name); + } + + @Override + public String[] getPropertyNames() { + return StringUtils.toStringArray(this.source.keySet()); + } + +} diff --git a/spring-test/src/main/resources/META-INF/spring.factories b/spring-test/src/main/resources/META-INF/spring.factories index b28b1e6a24..0237438cde 100644 --- a/spring-test/src/main/resources/META-INF/spring.factories +++ b/spring-test/src/main/resources/META-INF/spring.factories @@ -12,4 +12,5 @@ org.springframework.test.context.TestExecutionListener = \ # Default ContextCustomizerFactory implementations for the Spring TestContext Framework # org.springframework.test.context.ContextCustomizerFactory = \ - org.springframework.test.context.web.socket.MockServerContainerContextCustomizerFactory + org.springframework.test.context.web.socket.MockServerContainerContextCustomizerFactory,\ + org.springframework.test.context.support.DynamicPropertiesContextCustomizerFactory 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 new file mode 100644 index 0000000000..345cd38fa7 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/DynamicPropertySourceIntegrationTests.java @@ -0,0 +1,96 @@ +/* + * 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; + +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.stereotype.Component; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration test for {@link DynamicPropertySource @DynamicPropertySource}. + * + * @author Phillip Webb + * @author Sam Brannen + */ +@SpringJUnitConfig +class DynamicPropertySourceIntegrationTests { + + 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 + void hasInjectedValues(@Autowired Service service) { + assertThat(service.getIp()).isEqualTo("127.0.0.1"); + assertThat(service.getPort()).isEqualTo(4242); + } + + + @Configuration + @Import(Service.class) + static class Config { + } + + @Component + static class Service { + + private final String ip; + + private final int port; + + + Service(@Value("${test.container.ip}") String ip, @Value("${test.container.port}") 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/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 new file mode 100644 index 0000000000..4f58a1b65b --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/support/DynamicPropertiesContextCustomizerFactoryTests.java @@ -0,0 +1,121 @@ +/* + * 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.support; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import org.springframework.test.context.ContextConfigurationAttributes; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link DynamicPropertiesContextCustomizerFactory}. + * + * @author Phillip Webb + */ +class DynamicPropertiesContextCustomizerFactoryTests { + + private final DynamicPropertiesContextCustomizerFactory factory = new DynamicPropertiesContextCustomizerFactory(); + + private final List configAttributes = Collections.emptyList(); + + @Test + void createContextCustomizerWhenNoAnnotatedMethodsReturnsNull() { + DynamicPropertiesContextCustomizer customizer = this.factory.createContextCustomizer( + NoDynamicPropertySource.class, this.configAttributes); + assertThat(customizer).isNull(); + } + + @Test + void createContextCustomizerWhenSingleAnnotatedMethodReturnsCustomizer() { + DynamicPropertiesContextCustomizer customizer = this.factory.createContextCustomizer( + SingleDynamicPropertySource.class, this.configAttributes); + assertThat(customizer).isNotNull(); + assertThat(customizer.getMethods()).flatExtracting(Method::getName).containsOnly("p1"); + } + + @Test + void createContextCustomizerWhenMultipleAnnotatedMethodsReturnsCustomizer() { + DynamicPropertiesContextCustomizer customizer = this.factory.createContextCustomizer( + MultipleDynamicPropertySources.class, this.configAttributes); + assertThat(customizer).isNotNull(); + assertThat(customizer.getMethods()).flatExtracting(Method::getName).containsOnly("p1", "p2", "p3"); + } + + @Test + void createContextCustomizerWhenAnnotatedMethodsInBaseClassReturnsCustomizer() { + DynamicPropertiesContextCustomizer customizer = this.factory.createContextCustomizer( + SubDynamicPropertySource.class, this.configAttributes); + assertThat(customizer).isNotNull(); + assertThat(customizer.getMethods()).flatExtracting(Method::getName).containsOnly("p1", "p2"); + } + + + static class NoDynamicPropertySource { + + void empty() { + } + + } + + static class SingleDynamicPropertySource { + + @DynamicPropertySource + static void p1(DynamicPropertyRegistry registry) { + } + + } + + static class MultipleDynamicPropertySources { + + @DynamicPropertySource + static void p1(DynamicPropertyRegistry registry) { + } + + @DynamicPropertySource + static void p2(DynamicPropertyRegistry registry) { + } + + @DynamicPropertySource + static void p3(DynamicPropertyRegistry registry) { + } + + } + + static class BaseDynamicPropertySource { + + @DynamicPropertySource + static void p1(DynamicPropertyRegistry registry) { + } + + } + + static class SubDynamicPropertySource extends BaseDynamicPropertySource { + + @DynamicPropertySource + static void p2(DynamicPropertyRegistry registry) { + } + + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/support/DynamicPropertiesContextCustomizerTests.java b/spring-test/src/test/java/org/springframework/test/context/support/DynamicPropertiesContextCustomizerTests.java new file mode 100644 index 0000000000..8cc4b35e3a --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/support/DynamicPropertiesContextCustomizerTests.java @@ -0,0 +1,153 @@ +/* + * 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.support; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.StaticApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.MergedContextConfiguration; +import org.springframework.util.ObjectUtils; +import org.springframework.util.ReflectionUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link DynamicPropertiesContextCustomizer}. + * + * @author Phillip Webb + * @author Sam Brannen + */ +class DynamicPropertiesContextCustomizerTests { + + @Test + void createWhenNonStaticDynamicPropertiesMethodThrowsException() { + assertThatIllegalStateException() + .isThrownBy(() -> customizerFor("nonStatic")) + .withMessage("@DynamicPropertySource method 'nonStatic' must be static"); + } + + @Test + void createWhenBadDynamicPropertiesSignatureThrowsException() { + assertThatIllegalStateException() + .isThrownBy(() -> customizerFor("badArgs")) + .withMessage("@DynamicPropertySource method 'badArgs' must accept a single DynamicPropertyRegistry argument"); + } + + @Test + void nullPropertyNameResultsInException() throws Exception { + DynamicPropertiesContextCustomizer customizer = customizerFor("nullName"); + ConfigurableApplicationContext context = new StaticApplicationContext(); + assertThatIllegalArgumentException() + .isThrownBy(() -> customizer.customizeContext(context, mock(MergedContextConfiguration.class))) + .withMessage("'name' must not be null or blank"); + } + + @Test + void emptyPropertyNameResultsInException() throws Exception { + DynamicPropertiesContextCustomizer customizer = customizerFor("emptyName"); + ConfigurableApplicationContext context = new StaticApplicationContext(); + assertThatIllegalArgumentException() + .isThrownBy(() -> customizer.customizeContext(context, mock(MergedContextConfiguration.class))) + .withMessage("'name' must not be null or blank"); + } + + @Test + void nullValueSupplierResultsInException() throws Exception { + DynamicPropertiesContextCustomizer customizer = customizerFor("nullValueSupplier"); + ConfigurableApplicationContext context = new StaticApplicationContext(); + assertThatIllegalArgumentException() + .isThrownBy(() -> customizer.customizeContext(context, mock(MergedContextConfiguration.class))) + .withMessage("'valueSupplier' must not be null"); + } + + @Test + void customizeContextAddsPropertySource() throws Exception { + ConfigurableApplicationContext context = new StaticApplicationContext(); + DynamicPropertiesContextCustomizer customizer = customizerFor("valid1", "valid2"); + customizer.customizeContext(context, mock(MergedContextConfiguration.class)); + ConfigurableEnvironment environment = context.getEnvironment(); + assertThat(environment.getRequiredProperty("p1a")).isEqualTo("v1a"); + assertThat(environment.getRequiredProperty("p1b")).isEqualTo("v1b"); + assertThat(environment.getRequiredProperty("p2a")).isEqualTo("v2a"); + assertThat(environment.getRequiredProperty("p2b")).isEqualTo("v2b"); + } + + @Test + void equalsAndHashCode() { + DynamicPropertiesContextCustomizer c1 = customizerFor("valid1", "valid2"); + DynamicPropertiesContextCustomizer c2 = customizerFor("valid1", "valid2"); + DynamicPropertiesContextCustomizer c3 = customizerFor("valid1"); + assertThat(c1.hashCode()).isEqualTo(c1.hashCode()).isEqualTo(c2.hashCode()); + assertThat(c1).isEqualTo(c1).isEqualTo(c2).isNotEqualTo(c3); + } + + + private static DynamicPropertiesContextCustomizer customizerFor(String...methods) { + return new DynamicPropertiesContextCustomizer(findMethods(methods)); + } + + private static Set findMethods(String... names) { + Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(DynamicPropertySourceTestCase.class, + method -> ObjectUtils.containsElement(names, method.getName())); + return new LinkedHashSet<>(Arrays.asList(methods)); + } + + + static class DynamicPropertySourceTestCase { + + void nonStatic(DynamicPropertyRegistry registry) { + } + + static void badArgs(String bad) { + } + + static void nullName(DynamicPropertyRegistry registry) { + registry.add(null, () -> "A"); + } + + static void emptyName(DynamicPropertyRegistry registry) { + registry.add(" ", () -> "A"); + } + + static void nullValueSupplier(DynamicPropertyRegistry registry) { + registry.add("name", null); + } + + static void valid1(DynamicPropertyRegistry registry) { + registry.add("p1a", () -> "v1a"); + registry.add("p1b", () -> "v1b"); + } + + static void valid2(DynamicPropertyRegistry registry) { + registry.add("p2a", () -> "v2a"); + registry.add("p2b", () -> "v2b"); + } + + } + +} 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 new file mode 100644 index 0000000000..632eaf8f08 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/support/DynamicValuesPropertySourceTests.java @@ -0,0 +1,69 @@ +/* + * 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.support; + +import java.util.HashMap; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit tests for {@link DynamicValuesPropertySource}. + * + * @author Phillip Webb + * @author Sam Brannen + */ +class DynamicValuesPropertySourceTests { + + @SuppressWarnings("serial") + private final DynamicValuesPropertySource source = new DynamicValuesPropertySource("test", + new HashMap>() {{ + put("a", () -> "A"); + put("b", () -> "B"); + }}); + + + @Test + void getPropertyReturnsSuppliedProperty() throws Exception { + assertThat(this.source.getProperty("a")).isEqualTo("A"); + assertThat(this.source.getProperty("b")).isEqualTo("B"); + } + + @Test + void getPropertyWhenMissingReturnsNull() throws Exception { + assertThat(this.source.getProperty("c")).isNull(); + } + + @Test + void containsPropertyWhenPresentReturnsTrue() { + assertThat(this.source.containsProperty("a")).isTrue(); + assertThat(this.source.containsProperty("b")).isTrue(); + } + + @Test + void containsPropertyWhenMissingReturnsFalse() { + assertThat(this.source.containsProperty("c")).isFalse(); + } + + @Test + void getPropertyNamesReturnsNames() { + assertThat(this.source.getPropertyNames()).containsExactly("a", "b"); + } + +}