Add @DynamicPropertySource support in TestContext framework
This commit introduces a @DynamicPropertySource annotation that can be used on methods in test classes that want to add properties to the Environment with a dynamically supplied value. This new feature can be used in conjunction with Testcontainers and other frameworks that manage resources outside the lifecycle of a test's ApplicationContext. Closes gh-24540 Co-authored-by: Phillip Webb <pwebb@pivotal.io>
This commit is contained in:
@@ -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<Object> valueSupplier);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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}.
|
||||||
|
*
|
||||||
|
* <p>This annotation and its supporting infrastructure were originally designed
|
||||||
|
* to allow properties from
|
||||||
|
* <a href="https://www.testcontainers.org/">Testcontainers</a> 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}.
|
||||||
|
*
|
||||||
|
* <p>Methods annotated with {@code @DynamicPropertySource} must be {@code static}
|
||||||
|
* and must have a single {@link DynamicPropertyRegistry} argument which is used
|
||||||
|
* to add <em>name-value</em> 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.
|
||||||
|
*
|
||||||
|
* <h3>Example</h3>
|
||||||
|
*
|
||||||
|
* <pre class="code">
|
||||||
|
* @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);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* }</pre>
|
||||||
|
*
|
||||||
|
* @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 {
|
||||||
|
}
|
||||||
@@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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
|
* @author Sam Brannen
|
||||||
* @since 4.1
|
* @since 4.1
|
||||||
* @see ContextConfiguration
|
* @see ContextConfiguration
|
||||||
|
* @see DynamicPropertySource
|
||||||
* @see org.springframework.core.env.Environment
|
* @see org.springframework.core.env.Environment
|
||||||
* @see org.springframework.core.env.PropertySource
|
* @see org.springframework.core.env.PropertySource
|
||||||
* @see org.springframework.context.annotation.PropertySource
|
* @see org.springframework.context.annotation.PropertySource
|
||||||
|
|||||||
@@ -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<Method> methods;
|
||||||
|
|
||||||
|
|
||||||
|
DynamicPropertiesContextCustomizer(Set<Method> 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<String, Supplier<Object>> buildDynamicPropertiesMap() {
|
||||||
|
Map<String, Supplier<Object>> 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<Method> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<ContextConfigurationAttributes> configAttributes) {
|
||||||
|
|
||||||
|
Set<Method> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<Map<String, Supplier<Object>>> {
|
||||||
|
|
||||||
|
DynamicValuesPropertySource(String name, Map<String, Supplier<Object>> valueSuppliers) {
|
||||||
|
super(name, valueSuppliers);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getProperty(String name) {
|
||||||
|
Supplier<Object> 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -12,4 +12,5 @@ org.springframework.test.context.TestExecutionListener = \
|
|||||||
# Default ContextCustomizerFactory implementations for the Spring TestContext Framework
|
# Default ContextCustomizerFactory implementations for the Spring TestContext Framework
|
||||||
#
|
#
|
||||||
org.springframework.test.context.ContextCustomizerFactory = \
|
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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<ContextConfigurationAttributes> 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) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<Method> 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<String, Supplier<Object>>() {{
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user