Unify use of @BootStrapWith

Update @IntegrationTest to use @BootstrapWith rather than an explicitly
defined set of test execution listeners.

Also introduce a new @SpringApplicationTest annotation that is similar
to  @SpringApplicationConfiguration but a bootstrapper.

Fixes gh-5230
This commit is contained in:
Phillip Webb 2016-03-11 23:19:48 -08:00
parent 90950cfb1c
commit 7dffb702b5
14 changed files with 537 additions and 172 deletions

View File

@ -16,16 +16,56 @@
package org.springframework.boot.test;
import org.springframework.boot.test.context.IntegrationTest;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
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}.
*
* @author Dave Syer
* @author Phillip Webb
* @since 1.2.0
* @deprecated since 1.4.0 in favor of IntegrationTestPropertiesListener
* @deprecated since 1.4.0 as no longer used by {@code @IntegrationTest}.
*/
@Deprecated
public class IntegrationTestPropertiesListener
extends org.springframework.boot.test.context.IntegrationTestPropertiesListener {
public class IntegrationTestPropertiesListener extends AbstractTestExecutionListener {
@Override
public void prepareTestInstance(TestContext testContext) throws Exception {
Class<?> testClass = testContext.getTestClass();
AnnotationAttributes annotationAttributes = AnnotatedElementUtils
.getMergedAnnotationAttributes(testClass,
IntegrationTest.class.getName());
if (annotationAttributes != null) {
addPropertySourceProperties(testContext,
annotationAttributes.getStringArray("value"));
}
}
private void addPropertySourceProperties(TestContext testContext,
String[] properties) {
try {
MergedContextConfiguration configuration = (MergedContextConfiguration) ReflectionTestUtils
.getField(testContext, "mergedContextConfiguration");
new MergedContextConfigurationProperties(configuration).add(properties);
}
catch (RuntimeException ex) {
throw ex;
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.test.context;
package org.springframework.boot.test;
import java.util.Arrays;
import java.util.LinkedHashSet;
@ -28,14 +28,14 @@ import org.springframework.test.util.ReflectionTestUtils;
* Provides access to {@link MergedContextConfiguration} properties.
*
* @author Phillip Webb
* @since 1.4.0
* @deprecated since 1.4.0 along with {@link IntegrationTestPropertiesListener}
*/
public class MergedContextConfigurationProperties {
@Deprecated
class MergedContextConfigurationProperties {
private final MergedContextConfiguration configuration;
public MergedContextConfigurationProperties(
MergedContextConfiguration configuration) {
MergedContextConfigurationProperties(MergedContextConfiguration configuration) {
this.configuration = configuration;
}

View File

@ -17,6 +17,7 @@
package org.springframework.boot.test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.test.context.SpringApplicationTest;
import org.springframework.mock.web.MockServletContext;
import org.springframework.test.context.ContextLoader;
@ -35,11 +36,16 @@ import org.springframework.test.context.ContextLoader;
* @author Dave Syer
* @author Phillip Webb
* @author Andy Wilkinson
* @see IntegrationTest
* @see WebIntegrationTest
* @see TestRestTemplate
* @see org.springframework.boot.test.context.SpringApplicationTest
* @see org.springframework.boot.test.context.IntegrationTest
* @see org.springframework.boot.test.context.web.WebIntegrationTest
* @deprecated since 1.4.0 in favor of
* {@link org.springframework.boot.test.context.SpringApplicationContextLoader}
* {@link SpringApplicationTest @SpringApplicationTest},
* {@link org.springframework.boot.test.context.IntegrationTest @IntegrationTest},
* {@link org.springframework.boot.test.context.web.WebIntegrationTest @WebIntegrationTest}
* annotations.
* {@link org.springframework.boot.test.context.SpringApplicationContextLoader} can also
* be considered if absolutely necessary.
*/
@Deprecated
public class SpringApplicationContextLoader

View File

@ -23,36 +23,42 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.context.BootstrapWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
/**
* Test class annotation signifying that the tests are "integration tests" and therefore
* require full startup in the same way as a production application. Normally used in
* conjunction with {@code @SpringApplicationConfiguration}.
* Test class annotation signifying that the tests are "integration tests" for a
* {@link org.springframework.boot.SpringApplication Spring Boot Application}. By default
* will load nested {@code @Configuration} classes, or fallback an
* {@link SpringApplicationConfiguration @SpringApplicationConfiguration} search. Unless
* otherwise configured, a {@link SpringApplicationContextLoader} will be used to load the
* {@link ApplicationContext}. Use
* {@link SpringApplicationConfiguration @SpringApplicationConfiguration} or
* {@link ContextConfiguration @ContextConfiguration} if custom configuration is required.
* <p>
* If your test also uses {@code @WebAppConfiguration} consider using the
* {@link org.springframework.boot.test.context.web.WebIntegrationTest} instead.
* It's recommended that {@code @IntegrationTest} is used only for non-web applications
* (i.e. not combined with {@link WebAppConfiguration @WebAppConfiguration}). If you want
* to start a real embedded servlet container in the same way as a production application
* (listening on normal ports) use
* {@link org.springframework.boot.test.context.web.WebIntegrationTest @WebIntegrationTest}
* instead. If you are testing a web application and want to mock the servlet environment
* (for example so that you can use {@link MockMvc}) you should switch to the
* {@link SpringApplicationTest @SpringApplicationTest} annotation.
*
* @author Dave Syer
* @author Phillip Webb
* @see SpringApplicationTest
* @see org.springframework.boot.test.context.web.WebIntegrationTest
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@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 = { IntegrationTestPropertiesListener.class,
DirtiesContextBeforeModesTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class, SqlScriptsTestExecutionListener.class })
@BootstrapWith(IntegrationTestContextBootstrapper.class)
public @interface IntegrationTest {
/**

View File

@ -0,0 +1,73 @@
/*
* Copyright 2012-2016 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.context;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.TestContextBootstrapper;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.context.web.WebMergedContextConfiguration;
/**
* {@link TestContextBootstrapper} for {@link IntegrationTest}.
*
* @author Phillip Webb
*/
class IntegrationTestContextBootstrapper extends SpringBootTestContextBootstrapper {
private static final String SERVLET_LISTENER = "org.springframework.test.context.web.ServletTestExecutionListener";
@Override
protected List<String> getDefaultTestExecutionListenerClassNames() {
// Remove the ServletTestExecutionListener because it only deals with MockServlet
List<String> classNames = new ArrayList<String>(
super.getDefaultTestExecutionListenerClassNames());
while (classNames.contains(SERVLET_LISTENER)) {
classNames.remove(SERVLET_LISTENER);
}
return Collections.unmodifiableList(classNames);
}
@Override
protected MergedContextConfiguration processMergedContextConfiguration(
MergedContextConfiguration mergedConfig) {
mergedConfig = super.processMergedContextConfiguration(mergedConfig);
WebAppConfiguration webAppConfiguration = AnnotatedElementUtils
.getMergedAnnotation(mergedConfig.getTestClass(),
WebAppConfiguration.class);
if (webAppConfiguration != null) {
mergedConfig = new WebMergedContextConfiguration(mergedConfig,
webAppConfiguration.value());
}
return mergedConfig;
}
@Override
protected void processPropertySourceProperties(
MergedContextConfiguration mergedConfig,
List<String> propertySourceProperties) {
IntegrationTest annotation = AnnotatedElementUtils
.getMergedAnnotation(mergedConfig.getTestClass(), IntegrationTest.class);
propertySourceProperties.addAll(Arrays.asList(annotation.value()));
}
}

View File

@ -1,68 +0,0 @@
/*
* Copyright 2013-2016 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.context;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
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}.
*
* @author Dave Syer
* @author Phillip Webb
* @since 1.4.0
*/
public class IntegrationTestPropertiesListener extends AbstractTestExecutionListener {
@Override
public void prepareTestInstance(TestContext testContext) throws Exception {
Class<?> testClass = testContext.getTestClass();
AnnotationAttributes annotationAttributes = AnnotatedElementUtils
.getMergedAnnotationAttributes(testClass,
IntegrationTest.class.getName());
if (annotationAttributes != null) {
addPropertySourceProperties(testContext,
annotationAttributes.getStringArray("value"));
}
}
private void addPropertySourceProperties(TestContext testContext,
String[] properties) {
try {
MergedContextConfiguration configuration = (MergedContextConfiguration) ReflectionTestUtils
.getField(testContext, "mergedContextConfiguration");
new MergedContextConfigurationProperties(configuration).add(properties);
}
catch (RuntimeException ex) {
throw ex;
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}

View File

@ -34,9 +34,20 @@ import org.springframework.test.context.ContextConfiguration;
* <p>
* Similar to the standard {@link ContextConfiguration @ContextConfiguration} but uses
* Spring Boot's {@link SpringApplicationContextLoader}.
* <p>
* Tests that using this annotation only to define {@code classes} should consider using
* {@link SpringApplicationTest @SpringApplicationTest},
* {@link IntegrationTest @IntegrationTest} or
* {@link org.springframework.boot.test.context.web.WebIntegrationTest @WebIntegrationTest}
* instead.
*
* @author Dave Syer
* @author Sam Brannen
* @author Phillip Webb
* @since 1.4.0
* @see SpringApplicationTest
* @see IntegrationTest
* @see org.springframework.boot.test.context.web.WebIntegrationTest
* @see SpringApplicationContextLoader
* @see ContextConfiguration
*/

View File

@ -16,7 +16,6 @@
package org.springframework.boot.test.context;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -29,20 +28,16 @@ import java.util.Set;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.web.ServletContextApplicationContextInitializer;
import org.springframework.boot.test.context.web.WebIntegrationTest;
import org.springframework.boot.test.mock.web.SpringBootMockServletContext;
import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.SpringVersion;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.mock.web.MockServletContext;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextLoader;
@ -50,7 +45,6 @@ import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.support.AbstractContextLoader;
import org.springframework.test.context.support.AnnotationConfigContextLoaderUtils;
import org.springframework.test.context.support.TestPropertySourceUtils;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.context.web.WebMergedContextConfiguration;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
@ -59,12 +53,22 @@ import org.springframework.web.context.support.GenericWebApplicationContext;
/**
* A {@link ContextLoader} that can be used to test Spring Boot applications (those that
* normally startup using {@link SpringApplication}). Can be used to test non-web features
* (like a repository layer) or start an fully-configured embedded servlet container.
* <p>
* Use {@code @WebIntegrationTest} (or {@code @IntegrationTest} with
* {@code @WebAppConfiguration}) to indicate that you want to use a real servlet container
* or {@code @WebAppConfiguration} alone to use a {@link MockServletContext}.
* normally startup using {@link SpringApplication}). Although this loader can be used
* directly, most test will instead want to use one of the following annotations:
* <ul>
* <li>{@link SpringApplicationTest @SpringApplicationTest} - For non-web applications, or
* web-applications running under a mock servlet environment</li>
* <li>{@link IntegrationTest @IntegrationTest} - To integration test non-web applications
* </li>
* <li>
* {@link org.springframework.boot.test.context.web.WebIntegrationTest @WebIntegrationTest}
* - To integration test web applications (i.e. listening on normal ports).</li>
* </ul>
* The loader supports both standard {@link MergedContextConfiguration} as well as
* {@link WebMergedContextConfiguration}. If {@link WebMergedContextConfiguration} is used
* the context will either use a mock servlet environment, or start the full embedded
* servlet container (depending on the result of {@link #isIntegrationTest
* isIntegrationTest(...)}).
* <p>
* If {@code @ActiveProfiles} are provided in the test class they will be used to create
* the application context.
@ -72,16 +76,26 @@ import org.springframework.web.context.support.GenericWebApplicationContext;
* @author Dave Syer
* @author Phillip Webb
* @author Andy Wilkinson
* @see SpringApplicationTest
* @see IntegrationTest
* @see WebIntegrationTest
* @see TestRestTemplate
* @see org.springframework.boot.test.context.web.WebIntegrationTest
*/
public class SpringApplicationContextLoader extends AbstractContextLoader {
private static final Set<String> INTEGRATION_TEST_ANNOTATIONS;
static {
Set<String> annotations = new LinkedHashSet<String>();
annotations.add("org.springframework.boot.test.context.IntegrationTest");
annotations.add("org.springframework.boot.test.context.web.WebIntegrationTest");
annotations.add("org.springframework.boot.test.IntegrationTest");
annotations.add("org.springframework.boot.test.WebIntegrationTest");
INTEGRATION_TEST_ANNOTATIONS = Collections.unmodifiableSet(annotations);
}
@Override
public ApplicationContext loadContext(final MergedContextConfiguration config)
public ApplicationContext loadContext(MergedContextConfiguration config)
throws Exception {
assertValidAnnotations(config.getTestClass());
SpringApplication application = getSpringApplication();
application.setMainApplicationClass(config.getTestClass());
application.setSources(getSources(config));
@ -95,7 +109,9 @@ public class SpringApplicationContextLoader extends AbstractContextLoader {
List<ApplicationContextInitializer<?>> initializers = getInitializers(config,
application);
if (config instanceof WebMergedContextConfiguration) {
new WebConfigurer().configure(config, application, initializers);
application.setWebEnvironment(true);
WebConfigurer configurer = new WebConfigurer(isIntegrationTest(config));
configurer.configure(config, application, initializers);
}
else {
application.setWebEnvironment(false);
@ -105,15 +121,6 @@ public class SpringApplicationContextLoader extends AbstractContextLoader {
return context;
}
private void assertValidAnnotations(Class<?> testClass) {
if (AnnotatedElementUtils.isAnnotated(testClass, WebAppConfiguration.class)
&& AnnotatedElementUtils.isAnnotated(testClass,
WebIntegrationTest.class)) {
throw new IllegalStateException("@WebIntegrationTest and "
+ "@WebAppConfiguration cannot be used together");
}
}
/**
* Builds new {@link org.springframework.boot.SpringApplication} instance. You can
* override this method to add custom behavior
@ -147,7 +154,7 @@ public class SpringApplicationContextLoader extends AbstractContextLoader {
disableJmx(properties);
properties.putAll(TestPropertySourceUtils
.convertInlinedPropertiesToMap(config.getPropertySourceProperties()));
if (!TestAnnotations.isIntegrationTest(config)) {
if (!isIntegrationTest(config)) {
properties.putAll(getDefaultEnvironmentProperties());
}
return properties;
@ -185,6 +192,21 @@ public class SpringApplicationContextLoader extends AbstractContextLoader {
return initializers;
}
/**
* Return if the test is a full integration test or not. By default this method checks
* for well known integration test annotations.
* @param config the merged context configuration
* @return if the test is an integration test
*/
protected boolean isIntegrationTest(MergedContextConfiguration config) {
for (String annotation : INTEGRATION_TEST_ANNOTATIONS) {
if (AnnotatedElementUtils.isAnnotated(config.getTestClass(), annotation)) {
return true;
}
}
return false;
}
@Override
public void processContextConfiguration(
ContextConfigurationAttributes configAttributes) {
@ -233,10 +255,16 @@ public class SpringApplicationContextLoader extends AbstractContextLoader {
private static final Class<GenericWebApplicationContext> WEB_CONTEXT_CLASS = GenericWebApplicationContext.class;
private final boolean integrationTest;
WebConfigurer(boolean integrationTest) {
this.integrationTest = integrationTest;
}
void configure(MergedContextConfiguration configuration,
SpringApplication application,
List<ApplicationContextInitializer<?>> initializers) {
if (!TestAnnotations.isIntegrationTest(configuration)) {
if (!this.integrationTest) {
WebMergedContextConfiguration webConfiguration = (WebMergedContextConfiguration) configuration;
addMockServletContext(initializers, webConfiguration);
application.setApplicationContextClass(WEB_CONTEXT_CLASS);
@ -248,8 +276,8 @@ public class SpringApplicationContextLoader extends AbstractContextLoader {
WebMergedContextConfiguration webConfiguration) {
SpringBootMockServletContext servletContext = new SpringBootMockServletContext(
webConfiguration.getResourceBasePath());
initializers.add(0,
new ServletContextApplicationContextInitializer(servletContext));
initializers.add(0, new ServletContextApplicationContextInitializer(
servletContext, true));
}
}
@ -274,22 +302,6 @@ public class SpringApplicationContextLoader extends AbstractContextLoader {
}
private static class TestAnnotations {
public static boolean isIntegrationTest(
MergedContextConfiguration configuration) {
return (hasAnnotation(configuration, IntegrationTest.class)
|| hasAnnotation(configuration, WebIntegrationTest.class));
}
private static boolean hasAnnotation(MergedContextConfiguration configuration,
Class<? extends Annotation> annotation) {
return (AnnotationUtils.findAnnotation(configuration.getTestClass(),
annotation) != null);
}
}
/**
* Adapts a {@link ContextCustomizer} to a {@link ApplicationContextInitializer} so
* that it can be triggered via {@link SpringApplication}.

View File

@ -0,0 +1,67 @@
/*
* Copyright 2012-2016 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.context;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.test.context.BootstrapWith;
import org.springframework.test.context.ContextConfiguration;
/**
* Test class annotation signifying that the tests are for a
* {@link org.springframework.boot.SpringApplication Spring Boot Application}. By default
* will load nested {@code @Configuration} classes, or fallback an
* {@link SpringApplicationConfiguration @SpringApplicationConfiguration} search. Unless
* otherwise configured, a {@link SpringApplicationContextLoader} will be used to load the
* {@link ApplicationContext}. Use
* {@link SpringApplicationConfiguration @SpringApplicationConfiguration} or
* {@link ContextConfiguration @ContextConfiguration} if custom configuration is required.
* <p>
* A mock servlet environment will used when this annotation is used to a test web
* application. If you want to start a real embedded servlet container in the same way as
* a production application (listening on normal ports) the
* {@link org.springframework.boot.test.context.web.WebIntegrationTest @WebIntegrationTest}
* annotation should be used instead. If you are testing a non-web application, and you
* don't need a mock servlet environment you should switch to
* {@link IntegrationTest @IntegrationTest}.
*
* @author Phillip Webb
* @since 1.4.0
* @see IntegrationTest
* @see org.springframework.boot.test.context.web.WebIntegrationTest
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(SpringApplicationTestContextBootstrapper.class)
public @interface SpringApplicationTest {
/**
* Properties in form {@literal key=value} that should be added to the Spring
* {@link Environment} before the test runs.
*/
String[] value() default {};
}

View File

@ -0,0 +1,78 @@
/*
* Copyright 2012-2016 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.context;
import java.util.Arrays;
import java.util.List;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.TestContextBootstrapper;
import org.springframework.test.context.web.ServletTestExecutionListener;
import org.springframework.test.context.web.WebMergedContextConfiguration;
import org.springframework.util.ClassUtils;
/**
* {@link TestContextBootstrapper} for {@link SpringApplicationTest}.
*
* @author Phillip Webb
*/
class SpringApplicationTestContextBootstrapper extends SpringBootTestContextBootstrapper {
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
@Override
public TestContext buildTestContext() {
TestContext context = super.buildTestContext();
if (isWebApplicationTest()) {
context.setAttribute(ServletTestExecutionListener.ACTIVATE_LISTENER, true);
}
return context;
}
@Override
protected MergedContextConfiguration processMergedContextConfiguration(
MergedContextConfiguration mergedConfig) {
mergedConfig = super.processMergedContextConfiguration(mergedConfig);
if (!(mergedConfig instanceof WebMergedContextConfiguration)
&& isWebApplicationTest()) {
mergedConfig = new WebMergedContextConfiguration(mergedConfig, "");
}
return mergedConfig;
}
private boolean isWebApplicationTest() {
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return false;
}
}
return true;
}
@Override
protected void processPropertySourceProperties(
MergedContextConfiguration mergedConfig,
List<String> propertySourceProperties) {
SpringApplicationTest annotation = AnnotatedElementUtils.getMergedAnnotation(
mergedConfig.getTestClass(), SpringApplicationTest.class);
propertySourceProperties.addAll(Arrays.asList(annotation.value()));
}
}

View File

@ -23,27 +23,43 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.test.context.IntegrationTest;
import org.springframework.boot.test.context.SpringApplicationConfiguration;
import org.springframework.boot.test.context.SpringApplicationContextLoader;
import org.springframework.boot.test.context.SpringApplicationTest;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.test.context.BootstrapWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.web.servlet.MockMvc;
/**
* Test class annotation signifying that the tests are "web integration tests" and
* Test class annotation signifying that the tests are "web integration tests" for a
* {@link org.springframework.boot.SpringApplication Spring Boot Application} and
* therefore require full startup in the same way as a production application (listening
* on normal ports). Normally used in conjunction with
* {@code @SpringApplicationConfiguration},
* on normal ports. By default will load nested {@code @Configuration} classes, or
* fallback an {@link SpringApplicationConfiguration @SpringApplicationConfiguration}
* search. Unless otherwise configured, a {@link SpringApplicationContextLoader} will be
* used to load the {@link ApplicationContext}. Use
* {@link SpringApplicationConfiguration @SpringApplicationConfiguration} or
* {@link ContextConfiguration @ContextConfiguration} if custom configuration is required.
* <p>
* This annotation can be used as an alternative to {@code @IntegrationTest} and
* {@code @WebAppConfiguration}.
* If you are not testing a web application consider using the
* {@link IntegrationTest @IntegrationTest} annotation instead. If you are testing a web
* application and want to mock the servlet environment (for example so that you can use
* {@link MockMvc}) you should switch to the
* {@link SpringApplicationTest @SpringApplicationTest} annotation.
*
* @author Phillip Webb
* @since 1.2.1
* @see org.springframework.boot.test.context.IntegrationTest
* @since 1.4.0
* @see SpringApplicationTest
* @see IntegrationTest
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@BootstrapWith(WebAppIntegrationTestContextBootstrapper.class)
@BootstrapWith(WebIntegrationTestContextBootstrapper.class)
public @interface WebIntegrationTest {
/**

View File

@ -16,11 +16,14 @@
package org.springframework.boot.test.context.web;
import org.springframework.boot.test.context.MergedContextConfigurationProperties;
import java.util.Arrays;
import java.util.List;
import org.springframework.boot.test.context.SpringBootTestContextBootstrapper;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.TestContextBootstrapper;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.context.web.WebMergedContextConfiguration;
/**
@ -28,26 +31,36 @@ import org.springframework.test.context.web.WebMergedContextConfiguration;
*
* @author Phillip Webb
*/
class WebAppIntegrationTestContextBootstrapper extends SpringBootTestContextBootstrapper {
class WebIntegrationTestContextBootstrapper extends SpringBootTestContextBootstrapper {
@Override
protected MergedContextConfiguration processMergedContextConfiguration(
MergedContextConfiguration mergedConfig) {
assertValidAnnotations(mergedConfig.getTestClass());
mergedConfig = super.processMergedContextConfiguration(mergedConfig);
WebIntegrationTest annotation = AnnotatedElementUtils.findMergedAnnotation(
mergedConfig.getTestClass(), WebIntegrationTest.class);
if (annotation != null) {
mergedConfig = new WebMergedContextConfiguration(mergedConfig, null);
MergedContextConfigurationProperties properties = new MergedContextConfigurationProperties(
mergedConfig);
if (annotation.randomPort()) {
properties.add(annotation.value(), "server.port:0");
}
else {
properties.add(annotation.value());
}
return new WebMergedContextConfiguration(mergedConfig, null);
}
private void assertValidAnnotations(Class<?> testClass) {
if (AnnotatedElementUtils.findMergedAnnotation(testClass,
WebAppConfiguration.class) != null
&& AnnotatedElementUtils.findMergedAnnotation(testClass,
WebIntegrationTest.class) != null) {
throw new IllegalStateException("@WebIntegrationTest and "
+ "@WebAppConfiguration cannot be used together");
}
}
@Override
protected void processPropertySourceProperties(
MergedContextConfiguration mergedConfig,
List<String> propertySourceProperties) {
WebIntegrationTest annotation = AnnotatedElementUtils.getMergedAnnotation(
mergedConfig.getTestClass(), WebIntegrationTest.class);
propertySourceProperties.addAll(Arrays.asList(annotation.value()));
if (annotation.randomPort()) {
propertySourceProperties.add("server.port=0");
}
return mergedConfig;
}
}

View File

@ -0,0 +1,87 @@
/*
* Copyright 2012-2016 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.context;
import javax.servlet.ServletContext;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SpringApplicationTest}
*
* @author Phillip Webb
*/
@RunWith(SpringRunner.class)
@SpringApplicationTest("value=123")
@DirtiesContext
public class SpringApplicationTestTests {
@Value("${value}")
private int value = 0;
@Autowired
private WebApplicationContext context;
@Autowired
private ServletContext servletContext;
@Test
public void annotationAttributesOverridePropertiesFile() throws Exception {
assertThat(this.value).isEqualTo(123);
}
@Test
public void validateWebApplicationContextIsSet() {
WebApplicationContext fromServletContext = WebApplicationContextUtils
.getWebApplicationContext(this.servletContext);
assertThat(fromServletContext).isSameAs(this.context);
}
@Test
public void setsRequestContextHolder() throws Exception {
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
assertThat(attributes).isNotNull();
}
@Configuration
@EnableWebMvc
protected static class Config {
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholder() {
return new PropertySourcesPlaceholderConfigurer();
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2010-2012 the original author or authors.
* Copyright 2010-2016 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.
@ -18,14 +18,17 @@ package org.springframework.boot.context.web;
import javax.servlet.ServletContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.core.Ordered;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.WebApplicationContext;
/**
* {@link ApplicationContextInitializer} for setting the servlet context.
*
* @author Dave Syer
* @author Phillip Webb
*/
public class ServletContextApplicationContextInitializer implements
ApplicationContextInitializer<ConfigurableWebApplicationContext>, Ordered {
@ -34,12 +37,27 @@ public class ServletContextApplicationContextInitializer implements
private final ServletContext servletContext;
private final boolean addApplicationContextAttribute;
/**
* Create a new {@link ServletContextApplicationContextInitializer} instance.
* @param servletContext the servlet that should be ultimately set.
*/
public ServletContextApplicationContextInitializer(ServletContext servletContext) {
this(servletContext, false);
}
/**
* Create a new {@link ServletContextApplicationContextInitializer} instance.
* @param servletContext the servlet that should be ultimately set.
* @param addApplicationContextAttribute if the {@link ApplicationContext} should be
* stored as an attribute in the {@link ServletContext}
* @since 1.4.0
*/
public ServletContextApplicationContextInitializer(ServletContext servletContext,
boolean addApplicationContextAttribute) {
this.servletContext = servletContext;
this.addApplicationContextAttribute = addApplicationContextAttribute;
}
public void setOrder(int order) {
@ -54,6 +72,12 @@ public class ServletContextApplicationContextInitializer implements
@Override
public void initialize(ConfigurableWebApplicationContext applicationContext) {
applicationContext.setServletContext(this.servletContext);
if (this.addApplicationContextAttribute) {
this.servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
applicationContext);
}
}
}