mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-09-03 04:26:12 +08:00
Integrate with @TestPropertySource
Spring 4.2 has a @TestPropertySource which has some of the features of @IntegrationTest. This change adds @TestPropertySource to the @IntegrationTest annotation, so that (for instance) the cache key for the context includes properties for the test. Since @IntegrationTest has slightly different semantics I do not propose to deprecate it. Users can use it or @TestPropertySource, the main difference being that with @IntegrationTest the Spring Boot context loader is aware of the annotation and it will set sensible defaults for server.port and spring.jmx.enabled. There are some reflection hacks to overcome the usual fortifications of Spring Test. Fixes gh-1697
This commit is contained in:
parent
261b3afca1
commit
d251b51338
@ -58,8 +58,8 @@ import static org.junit.Assert.assertTrue;
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = Application.class)
|
||||
@WebAppConfiguration
|
||||
@IntegrationTest("server.port=0")
|
||||
@WebAppConfiguration
|
||||
@DirtiesContext
|
||||
public class EndpointMvcIntegrationTests {
|
||||
|
||||
|
@ -25,6 +25,7 @@ import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
||||
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
|
||||
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
|
||||
@ -41,15 +42,21 @@ import org.springframework.test.context.transaction.TransactionalTestExecutionLi
|
||||
@Target(ElementType.TYPE)
|
||||
// Leave out the ServletTestExecutionListener because it only deals with Mock* servlet
|
||||
// stuff. A real embedded application will not need the mocks.
|
||||
@TestExecutionListeners(listeners = { DependencyInjectionTestExecutionListener.class,
|
||||
@TestExecutionListeners(listeners = { IntegrationTestPropertiesListener.class, DependencyInjectionTestExecutionListener.class,
|
||||
DirtiesContextTestExecutionListener.class,
|
||||
TransactionalTestExecutionListener.class })
|
||||
@TestPropertySource
|
||||
public @interface IntegrationTest {
|
||||
|
||||
/**
|
||||
* Synonym for properties().
|
||||
*/
|
||||
String[] value() default {};
|
||||
|
||||
/**
|
||||
* Properties in form {@literal key=value} that should be added to the Spring
|
||||
* {@link Environment} before the test runs.
|
||||
*/
|
||||
String[] value() default {};
|
||||
String[] properties() default {"server.port=-1", "spring.jmx.enabled=false"};
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright 2013-2104 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
|
||||
*
|
||||
* http://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.boot.test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.test.context.MergedContextConfiguration;
|
||||
import org.springframework.test.context.TestContext;
|
||||
import org.springframework.test.context.support.AbstractTestExecutionListener;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
/**
|
||||
* Manipulate the TestContext to merge properties from <code>@IntegrationTest</code> value
|
||||
* and properties attributes.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class IntegrationTestPropertiesListener extends AbstractTestExecutionListener {
|
||||
|
||||
private String[] defaultValues = (String[]) AnnotationUtils.getDefaultValue(
|
||||
IntegrationTest.class, "properties");
|
||||
|
||||
@Override
|
||||
public void prepareTestInstance(TestContext testContext) throws Exception {
|
||||
MergedContextConfiguration config = null;
|
||||
try {
|
||||
// Here be hacks...
|
||||
config = (MergedContextConfiguration) ReflectionTestUtils.getField(
|
||||
testContext, "mergedContextConfiguration");
|
||||
ReflectionTestUtils.setField(config, "propertySourceProperties",
|
||||
getEnvironmentProperties(config));
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
protected String[] getEnvironmentProperties(MergedContextConfiguration config) {
|
||||
IntegrationTest annotation = AnnotationUtils.findAnnotation(
|
||||
config.getTestClass(), IntegrationTest.class);
|
||||
return mergeProperties(
|
||||
getDefaultEnvironmentProperties(config.getPropertySourceProperties(),
|
||||
annotation), getEnvironmentProperties(annotation));
|
||||
}
|
||||
|
||||
private String[] getDefaultEnvironmentProperties(String[] original,
|
||||
IntegrationTest annotation) {
|
||||
String[] defaults = mergeProperties(original, defaultValues);
|
||||
if (annotation == null || defaults.length == 0) {
|
||||
// Without an @IntegrationTest we can assume the defaults are fine
|
||||
return defaults;
|
||||
}
|
||||
// If @IntegrationTest is present we don't provide a default for the server.port
|
||||
return filterPorts((String[]) AnnotationUtils.getDefaultValue(annotation,
|
||||
"properties"));
|
||||
}
|
||||
|
||||
private String[] filterPorts(String[] values) {
|
||||
|
||||
Set<String> result = new LinkedHashSet<String>();
|
||||
for (String value : values) {
|
||||
if (!value.contains(".port")) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
return result.toArray(new String[0]);
|
||||
|
||||
}
|
||||
|
||||
private String[] getEnvironmentProperties(IntegrationTest annotation) {
|
||||
if (annotation == null) {
|
||||
return new String[0];
|
||||
}
|
||||
if (Arrays.asList(annotation.properties()).equals(Arrays.asList(defaultValues))) {
|
||||
return annotation.value();
|
||||
}
|
||||
if (annotation.value().length == 0) {
|
||||
return annotation.properties();
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
"Either properties or value can be provided but not both");
|
||||
}
|
||||
|
||||
private String[] mergeProperties(String[] original, String[] extra) {
|
||||
Set<String> result = new LinkedHashSet<String>();
|
||||
for (String value : original) {
|
||||
result.add(value);
|
||||
}
|
||||
for (String value : extra) {
|
||||
result.add(value);
|
||||
}
|
||||
return result.toArray(new String[0]);
|
||||
}
|
||||
|
||||
}
|
@ -20,9 +20,7 @@ import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -92,7 +90,7 @@ public class SpringApplicationContextLoader extends AbstractContextLoader {
|
||||
.addAfter(
|
||||
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
|
||||
new MapPropertySource("integrationTest",
|
||||
getEnvironmentProperties(config)));
|
||||
extractEnvironmentProperties(config.getPropertySourceProperties())));
|
||||
application.setEnvironment(environment);
|
||||
List<ApplicationContextInitializer<?>> initializers = getInitializers(config,
|
||||
application);
|
||||
@ -107,6 +105,32 @@ public class SpringApplicationContextLoader extends AbstractContextLoader {
|
||||
return application.run();
|
||||
}
|
||||
|
||||
// Instead of parsing the keys ourselves, we rely on standard handling
|
||||
protected Map<String, Object> extractEnvironmentProperties(String[] values) {
|
||||
Map<String, Object> properties = new HashMap<String, Object>();
|
||||
if (values==null) {
|
||||
return properties;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String value : values) {
|
||||
sb.append(value).append(LINE_SEPARATOR);
|
||||
}
|
||||
String content = sb.toString();
|
||||
Properties props = new Properties();
|
||||
try {
|
||||
props.load(new StringReader(content));
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new IllegalStateException("Unexpected could not load properties from '"
|
||||
+ content + "'", e);
|
||||
}
|
||||
|
||||
for (String name : props.stringPropertyNames()) {
|
||||
properties.put(name, props.getProperty(name));
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processContextConfiguration(
|
||||
ContextConfigurationAttributes configAttributes) {
|
||||
@ -152,55 +176,8 @@ public class SpringApplicationContextLoader extends AbstractContextLoader {
|
||||
return AnnotationConfigContextLoaderUtils
|
||||
.detectDefaultConfigurationClasses(declaringClass);
|
||||
}
|
||||
|
||||
protected Map<String, Object> getEnvironmentProperties(
|
||||
MergedContextConfiguration config) {
|
||||
Map<String, Object> properties = new LinkedHashMap<String, Object>();
|
||||
// JMX bean names will clash if the same bean is used in multiple contexts
|
||||
disableJmx(properties);
|
||||
IntegrationTest annotation = AnnotationUtils.findAnnotation(
|
||||
config.getTestClass(), IntegrationTest.class);
|
||||
properties.putAll(getEnvironmentProperties(annotation));
|
||||
return properties;
|
||||
}
|
||||
|
||||
private void disableJmx(Map<String, Object> properties) {
|
||||
properties.put("spring.jmx.enabled", "false");
|
||||
}
|
||||
|
||||
private Map<String, String> getEnvironmentProperties(IntegrationTest annotation) {
|
||||
if (annotation == null) {
|
||||
return getDefaultEnvironmentProperties();
|
||||
}
|
||||
return extractEnvironmentProperties(annotation.value());
|
||||
}
|
||||
|
||||
private Map<String, String> getDefaultEnvironmentProperties() {
|
||||
return Collections.singletonMap("server.port", "-1");
|
||||
}
|
||||
|
||||
// Instead of parsing the keys ourselves, we rely on standard handling
|
||||
private Map<String, String> extractEnvironmentProperties(String[] values) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String value : values) {
|
||||
sb.append(value).append(LINE_SEPARATOR);
|
||||
}
|
||||
String content = sb.toString();
|
||||
Properties props = new Properties();
|
||||
try {
|
||||
props.load(new StringReader(content));
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new IllegalStateException("Unexpected could not load properties from '"
|
||||
+ content + "'", e);
|
||||
}
|
||||
|
||||
Map<String, String> properties = new HashMap<String, String>();
|
||||
for (String name : props.stringPropertyNames()) {
|
||||
properties.put(name, props.getProperty(name));
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private List<ApplicationContextInitializer<?>> getInitializers(
|
||||
MergedContextConfiguration mergedConfig, SpringApplication application) {
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.boot;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Suite;
|
||||
import org.junit.runners.Suite.SuiteClasses;
|
||||
@ -30,7 +31,7 @@ import org.springframework.boot.test.SpringApplicationConfigurationJmxTests;
|
||||
@RunWith(Suite.class)
|
||||
@SuiteClasses({ SpringApplicationConfigurationJmxTests.class,
|
||||
SpringApplicationConfigurationDefaultConfigurationTests.class })
|
||||
// @Ignore
|
||||
@Ignore
|
||||
public class AdhocTestSuite {
|
||||
|
||||
}
|
||||
|
@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.boot.test;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@ -25,8 +27,6 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
/**
|
||||
* Tests for disabling JMX by default
|
||||
*
|
||||
@ -34,6 +34,7 @@ import static org.junit.Assert.assertFalse;
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = Config.class)
|
||||
@IntegrationTest
|
||||
public class SpringApplicationConfigurationJmxTests {
|
||||
|
||||
@Value("${spring.jmx.enabled}")
|
||||
|
@ -16,15 +16,16 @@
|
||||
|
||||
package org.springframework.boot.test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.test.context.MergedContextConfiguration;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import org.springframework.test.context.TestContext;
|
||||
import org.springframework.test.context.TestContextManager;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
/**
|
||||
* Tests for {@link SpringApplicationContextLoader}
|
||||
@ -36,30 +37,57 @@ public class SpringApplicationContextLoaderTests {
|
||||
private final SpringApplicationContextLoader loader = new SpringApplicationContextLoader();
|
||||
|
||||
@Test
|
||||
public void environmentPropertiesSimple() {
|
||||
public void environmentPropertiesSimple() throws Exception {
|
||||
Map<String, Object> config = getEnvironmentProperties(SimpleConfig.class);
|
||||
assertKey(config, "key", "myValue");
|
||||
assertKey(config, "anotherKey", "anotherValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void environmentPropertiesSeparatorInValue() {
|
||||
public void environmentPropertiesDefaults() throws Exception {
|
||||
Map<String, Object> config = getEnvironmentProperties(SimpleConfig.class);
|
||||
assertMissingKey(config, "server.port");
|
||||
assertKey(config, "spring.jmx.enabled", "false");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void environmentPropertiesOverrideDefaults() throws Exception {
|
||||
Map<String, Object> config = getEnvironmentProperties(OverrideConfig.class);
|
||||
assertKey(config, "server.port", "2345");
|
||||
}
|
||||
|
||||
@Test(expected=IllegalStateException.class)
|
||||
public void environmentPropertiesIllegal() throws Exception {
|
||||
getEnvironmentProperties(IllegalConfig.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void environmentPropertiesAppend() throws Exception {
|
||||
Map<String, Object> config = getEnvironmentProperties(AppendConfig.class);
|
||||
assertKey(config, "key", "myValue");
|
||||
assertKey(config, "otherKey", "otherValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void environmentPropertiesSeparatorInValue() throws Exception {
|
||||
Map<String, Object> config = getEnvironmentProperties(SameSeparatorInValue.class);
|
||||
assertKey(config, "key", "my=Value");
|
||||
assertKey(config, "anotherKey", "another:Value");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void environmentPropertiesAnotherSeparatorInValue() {
|
||||
public void environmentPropertiesAnotherSeparatorInValue() throws Exception {
|
||||
Map<String, Object> config = getEnvironmentProperties(AnotherSeparatorInValue.class);
|
||||
assertKey(config, "key", "my:Value");
|
||||
assertKey(config, "anotherKey", "another=Value");
|
||||
}
|
||||
|
||||
private Map<String, Object> getEnvironmentProperties(Class<?> testClass) {
|
||||
MergedContextConfiguration configuration = mock(MergedContextConfiguration.class);
|
||||
doReturn(testClass).when(configuration).getTestClass();
|
||||
return this.loader.getEnvironmentProperties(configuration);
|
||||
private Map<String, Object> getEnvironmentProperties(Class<?> testClass) throws Exception {
|
||||
TestContext context = new ExposedTestContextManager(testClass).getExposedTestContext();
|
||||
new IntegrationTestPropertiesListener().prepareTestInstance(context);
|
||||
MergedContextConfiguration config = (MergedContextConfiguration) ReflectionTestUtils.getField(
|
||||
context, "mergedContextConfiguration");
|
||||
return this.loader.extractEnvironmentProperties(config.getPropertySourceProperties());
|
||||
}
|
||||
|
||||
private void assertKey(Map<String, Object> actual, String key, Object value) {
|
||||
@ -67,10 +95,26 @@ public class SpringApplicationContextLoaderTests {
|
||||
assertEquals(value, actual.get(key));
|
||||
}
|
||||
|
||||
private void assertMissingKey(Map<String, Object> actual, String key) {
|
||||
assertTrue("Key '" + key + "' found", !actual.containsKey(key));
|
||||
}
|
||||
|
||||
@IntegrationTest({ "key=myValue", "anotherKey:anotherValue" })
|
||||
static class SimpleConfig {
|
||||
}
|
||||
|
||||
@IntegrationTest({ "server.port=2345" })
|
||||
static class OverrideConfig {
|
||||
}
|
||||
|
||||
@IntegrationTest(value = { "key=aValue", "anotherKey:anotherValue" }, properties = { "key=myValue", "otherKey=otherValue" })
|
||||
static class IllegalConfig {
|
||||
}
|
||||
|
||||
@IntegrationTest(properties = { "key=myValue", "otherKey=otherValue" })
|
||||
static class AppendConfig {
|
||||
}
|
||||
|
||||
@IntegrationTest({ "key=my=Value", "anotherKey:another:Value" })
|
||||
static class SameSeparatorInValue {
|
||||
}
|
||||
@ -78,5 +122,18 @@ public class SpringApplicationContextLoaderTests {
|
||||
@IntegrationTest({ "key=my:Value", "anotherKey:another=Value" })
|
||||
static class AnotherSeparatorInValue {
|
||||
}
|
||||
|
||||
private static class ExposedTestContextManager extends TestContextManager {
|
||||
|
||||
public ExposedTestContextManager(Class<?> testClass) {
|
||||
super(testClass);
|
||||
}
|
||||
|
||||
public final TestContext getExposedTestContext() {
|
||||
return super.getTestContext();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user