Commit 7dffb702 authored by Phillip Webb's avatar Phillip Webb

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
parent 90950cfb
...@@ -16,16 +16,56 @@ ...@@ -16,16 +16,56 @@
package org.springframework.boot.test; 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}. * Manipulate the TestContext to merge properties from {@code @IntegrationTest}.
* *
* @author Dave Syer * @author Dave Syer
* @author Phillip Webb * @author Phillip Webb
* @since 1.2.0 * @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 @Deprecated
public class IntegrationTestPropertiesListener public class IntegrationTestPropertiesListener extends AbstractTestExecutionListener {
extends org.springframework.boot.test.context.IntegrationTestPropertiesListener {
@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;
}
} }
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.test.context; package org.springframework.boot.test;
import java.util.Arrays; import java.util.Arrays;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
...@@ -28,14 +28,14 @@ import org.springframework.test.util.ReflectionTestUtils; ...@@ -28,14 +28,14 @@ import org.springframework.test.util.ReflectionTestUtils;
* Provides access to {@link MergedContextConfiguration} properties. * Provides access to {@link MergedContextConfiguration} properties.
* *
* @author Phillip Webb * @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; private final MergedContextConfiguration configuration;
public MergedContextConfigurationProperties( MergedContextConfigurationProperties(MergedContextConfiguration configuration) {
MergedContextConfiguration configuration) {
this.configuration = configuration; this.configuration = configuration;
} }
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package org.springframework.boot.test; package org.springframework.boot.test;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.test.context.SpringApplicationTest;
import org.springframework.mock.web.MockServletContext; import org.springframework.mock.web.MockServletContext;
import org.springframework.test.context.ContextLoader; import org.springframework.test.context.ContextLoader;
...@@ -35,11 +36,16 @@ import org.springframework.test.context.ContextLoader; ...@@ -35,11 +36,16 @@ import org.springframework.test.context.ContextLoader;
* @author Dave Syer * @author Dave Syer
* @author Phillip Webb * @author Phillip Webb
* @author Andy Wilkinson * @author Andy Wilkinson
* @see IntegrationTest * @see org.springframework.boot.test.context.SpringApplicationTest
* @see WebIntegrationTest * @see org.springframework.boot.test.context.IntegrationTest
* @see TestRestTemplate * @see org.springframework.boot.test.context.web.WebIntegrationTest
* @deprecated since 1.4.0 in favor of * @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 @Deprecated
public class SpringApplicationContextLoader public class SpringApplicationContextLoader
......
...@@ -23,36 +23,42 @@ import java.lang.annotation.Retention; ...@@ -23,36 +23,42 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.BootstrapWith;
import org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
/** /**
* Test class annotation signifying that the tests are "integration tests" and therefore * Test class annotation signifying that the tests are "integration tests" for a
* require full startup in the same way as a production application. Normally used in * {@link org.springframework.boot.SpringApplication Spring Boot Application}. By default
* conjunction with {@code @SpringApplicationConfiguration}. * 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> * <p>
* If your test also uses {@code @WebAppConfiguration} consider using the * It's recommended that {@code @IntegrationTest} is used only for non-web applications
* {@link org.springframework.boot.test.context.web.WebIntegrationTest} instead. * (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 Dave Syer
* @author Phillip Webb
* @see SpringApplicationTest
* @see org.springframework.boot.test.context.web.WebIntegrationTest * @see org.springframework.boot.test.context.web.WebIntegrationTest
*/ */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
@Inherited @Inherited
@Retention(RetentionPolicy.RUNTIME) @BootstrapWith(IntegrationTestContextBootstrapper.class)
@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 })
public @interface IntegrationTest { public @interface IntegrationTest {
/** /**
......
/*
* 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()));
}
}
...@@ -34,9 +34,20 @@ import org.springframework.test.context.ContextConfiguration; ...@@ -34,9 +34,20 @@ import org.springframework.test.context.ContextConfiguration;
* <p> * <p>
* Similar to the standard {@link ContextConfiguration @ContextConfiguration} but uses * Similar to the standard {@link ContextConfiguration @ContextConfiguration} but uses
* Spring Boot's {@link SpringApplicationContextLoader}. * 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 Dave Syer
* @author Sam Brannen * @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 SpringApplicationContextLoader
* @see ContextConfiguration * @see ContextConfiguration
*/ */
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
package org.springframework.boot.test.context; package org.springframework.boot.test.context;
import java.lang.annotation.Annotation;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
...@@ -29,20 +28,16 @@ import java.util.Set; ...@@ -29,20 +28,16 @@ import java.util.Set;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.web.ServletContextApplicationContextInitializer; 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.mock.web.SpringBootMockServletContext;
import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.SpringVersion; import org.springframework.core.SpringVersion;
import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.StandardEnvironment; import org.springframework.core.env.StandardEnvironment;
import org.springframework.mock.web.MockServletContext;
import org.springframework.test.context.ContextConfigurationAttributes; import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer; import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextLoader; import org.springframework.test.context.ContextLoader;
...@@ -50,7 +45,6 @@ import org.springframework.test.context.MergedContextConfiguration; ...@@ -50,7 +45,6 @@ import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.support.AbstractContextLoader; import org.springframework.test.context.support.AbstractContextLoader;
import org.springframework.test.context.support.AnnotationConfigContextLoaderUtils; import org.springframework.test.context.support.AnnotationConfigContextLoaderUtils;
import org.springframework.test.context.support.TestPropertySourceUtils; import org.springframework.test.context.support.TestPropertySourceUtils;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.context.web.WebMergedContextConfiguration; import org.springframework.test.context.web.WebMergedContextConfiguration;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
...@@ -59,12 +53,22 @@ import org.springframework.web.context.support.GenericWebApplicationContext; ...@@ -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 * 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 * normally startup using {@link SpringApplication}). Although this loader can be used
* (like a repository layer) or start an fully-configured embedded servlet container. * directly, most test will instead want to use one of the following annotations:
* <p> * <ul>
* Use {@code @WebIntegrationTest} (or {@code @IntegrationTest} with * <li>{@link SpringApplicationTest @SpringApplicationTest} - For non-web applications, or
* {@code @WebAppConfiguration}) to indicate that you want to use a real servlet container * web-applications running under a mock servlet environment</li>
* or {@code @WebAppConfiguration} alone to use a {@link MockServletContext}. * <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> * <p>
* If {@code @ActiveProfiles} are provided in the test class they will be used to create * If {@code @ActiveProfiles} are provided in the test class they will be used to create
* the application context. * the application context.
...@@ -72,16 +76,26 @@ import org.springframework.web.context.support.GenericWebApplicationContext; ...@@ -72,16 +76,26 @@ import org.springframework.web.context.support.GenericWebApplicationContext;
* @author Dave Syer * @author Dave Syer
* @author Phillip Webb * @author Phillip Webb
* @author Andy Wilkinson * @author Andy Wilkinson
* @see SpringApplicationTest
* @see IntegrationTest * @see IntegrationTest
* @see WebIntegrationTest * @see org.springframework.boot.test.context.web.WebIntegrationTest
* @see TestRestTemplate
*/ */
public class SpringApplicationContextLoader extends AbstractContextLoader { 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 @Override
public ApplicationContext loadContext(final MergedContextConfiguration config) public ApplicationContext loadContext(MergedContextConfiguration config)
throws Exception { throws Exception {
assertValidAnnotations(config.getTestClass());
SpringApplication application = getSpringApplication(); SpringApplication application = getSpringApplication();
application.setMainApplicationClass(config.getTestClass()); application.setMainApplicationClass(config.getTestClass());
application.setSources(getSources(config)); application.setSources(getSources(config));
...@@ -95,7 +109,9 @@ public class SpringApplicationContextLoader extends AbstractContextLoader { ...@@ -95,7 +109,9 @@ public class SpringApplicationContextLoader extends AbstractContextLoader {
List<ApplicationContextInitializer<?>> initializers = getInitializers(config, List<ApplicationContextInitializer<?>> initializers = getInitializers(config,
application); application);
if (config instanceof WebMergedContextConfiguration) { 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 { else {
application.setWebEnvironment(false); application.setWebEnvironment(false);
...@@ -105,15 +121,6 @@ public class SpringApplicationContextLoader extends AbstractContextLoader { ...@@ -105,15 +121,6 @@ public class SpringApplicationContextLoader extends AbstractContextLoader {
return context; 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 * Builds new {@link org.springframework.boot.SpringApplication} instance. You can
* override this method to add custom behavior * override this method to add custom behavior
...@@ -147,7 +154,7 @@ public class SpringApplicationContextLoader extends AbstractContextLoader { ...@@ -147,7 +154,7 @@ public class SpringApplicationContextLoader extends AbstractContextLoader {
disableJmx(properties); disableJmx(properties);
properties.putAll(TestPropertySourceUtils properties.putAll(TestPropertySourceUtils
.convertInlinedPropertiesToMap(config.getPropertySourceProperties())); .convertInlinedPropertiesToMap(config.getPropertySourceProperties()));
if (!TestAnnotations.isIntegrationTest(config)) { if (!isIntegrationTest(config)) {
properties.putAll(getDefaultEnvironmentProperties()); properties.putAll(getDefaultEnvironmentProperties());
} }
return properties; return properties;
...@@ -185,6 +192,21 @@ public class SpringApplicationContextLoader extends AbstractContextLoader { ...@@ -185,6 +192,21 @@ public class SpringApplicationContextLoader extends AbstractContextLoader {
return initializers; 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 @Override
public void processContextConfiguration( public void processContextConfiguration(
ContextConfigurationAttributes configAttributes) { ContextConfigurationAttributes configAttributes) {
...@@ -233,10 +255,16 @@ public class SpringApplicationContextLoader extends AbstractContextLoader { ...@@ -233,10 +255,16 @@ public class SpringApplicationContextLoader extends AbstractContextLoader {
private static final Class<GenericWebApplicationContext> WEB_CONTEXT_CLASS = GenericWebApplicationContext.class; private static final Class<GenericWebApplicationContext> WEB_CONTEXT_CLASS = GenericWebApplicationContext.class;
private final boolean integrationTest;
WebConfigurer(boolean integrationTest) {
this.integrationTest = integrationTest;
}
void configure(MergedContextConfiguration configuration, void configure(MergedContextConfiguration configuration,
SpringApplication application, SpringApplication application,
List<ApplicationContextInitializer<?>> initializers) { List<ApplicationContextInitializer<?>> initializers) {
if (!TestAnnotations.isIntegrationTest(configuration)) { if (!this.integrationTest) {
WebMergedContextConfiguration webConfiguration = (WebMergedContextConfiguration) configuration; WebMergedContextConfiguration webConfiguration = (WebMergedContextConfiguration) configuration;
addMockServletContext(initializers, webConfiguration); addMockServletContext(initializers, webConfiguration);
application.setApplicationContextClass(WEB_CONTEXT_CLASS); application.setApplicationContextClass(WEB_CONTEXT_CLASS);
...@@ -248,8 +276,8 @@ public class SpringApplicationContextLoader extends AbstractContextLoader { ...@@ -248,8 +276,8 @@ public class SpringApplicationContextLoader extends AbstractContextLoader {
WebMergedContextConfiguration webConfiguration) { WebMergedContextConfiguration webConfiguration) {
SpringBootMockServletContext servletContext = new SpringBootMockServletContext( SpringBootMockServletContext servletContext = new SpringBootMockServletContext(
webConfiguration.getResourceBasePath()); webConfiguration.getResourceBasePath());
initializers.add(0, initializers.add(0, new ServletContextApplicationContextInitializer(
new ServletContextApplicationContextInitializer(servletContext)); servletContext, true));
} }
} }
...@@ -274,22 +302,6 @@ public class SpringApplicationContextLoader extends AbstractContextLoader { ...@@ -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 * Adapts a {@link ContextCustomizer} to a {@link ApplicationContextInitializer} so
* that it can be triggered via {@link SpringApplication}. * that it can be triggered via {@link SpringApplication}.
......
/*
* 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 {};
}
/* /*
* Copyright 2013-2016 the original author or authors. * Copyright 2012-2016 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.
...@@ -16,53 +16,63 @@ ...@@ -16,53 +16,63 @@
package org.springframework.boot.test.context; package org.springframework.boot.test.context;
import org.springframework.core.Ordered; import java.util.Arrays;
import java.util.List;
import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.test.context.MergedContextConfiguration; import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.TestContext; import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener; import org.springframework.test.context.TestContextBootstrapper;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.context.web.ServletTestExecutionListener;
import org.springframework.test.context.web.WebMergedContextConfiguration;
import org.springframework.util.ClassUtils;
/** /**
* Manipulate the TestContext to merge properties from {@code @IntegrationTest}. * {@link TestContextBootstrapper} for {@link SpringApplicationTest}.
* *
* @author Dave Syer
* @author Phillip Webb * @author Phillip Webb
* @since 1.4.0
*/ */
public class IntegrationTestPropertiesListener extends AbstractTestExecutionListener { class SpringApplicationTestContextBootstrapper extends SpringBootTestContextBootstrapper {
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
@Override @Override
public void prepareTestInstance(TestContext testContext) throws Exception { public TestContext buildTestContext() {
Class<?> testClass = testContext.getTestClass(); TestContext context = super.buildTestContext();
AnnotationAttributes annotationAttributes = AnnotatedElementUtils if (isWebApplicationTest()) {
.getMergedAnnotationAttributes(testClass, context.setAttribute(ServletTestExecutionListener.ACTIVATE_LISTENER, true);
IntegrationTest.class.getName());
if (annotationAttributes != null) {
addPropertySourceProperties(testContext,
annotationAttributes.getStringArray("value"));
} }
return context;
} }
private void addPropertySourceProperties(TestContext testContext, @Override
String[] properties) { protected MergedContextConfiguration processMergedContextConfiguration(
try { MergedContextConfiguration mergedConfig) {
MergedContextConfiguration configuration = (MergedContextConfiguration) ReflectionTestUtils mergedConfig = super.processMergedContextConfiguration(mergedConfig);
.getField(testContext, "mergedContextConfiguration"); if (!(mergedConfig instanceof WebMergedContextConfiguration)
new MergedContextConfigurationProperties(configuration).add(properties); && isWebApplicationTest()) {
} mergedConfig = new WebMergedContextConfiguration(mergedConfig, "");
catch (RuntimeException ex) {
throw ex;
} }
catch (Exception ex) { return mergedConfig;
throw new IllegalStateException(ex); }
private boolean isWebApplicationTest() {
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return false;
}
} }
return true;
} }
@Override @Override
public int getOrder() { protected void processPropertySourceProperties(
return Ordered.HIGHEST_PRECEDENCE; MergedContextConfiguration mergedConfig,
List<String> propertySourceProperties) {
SpringApplicationTest annotation = AnnotatedElementUtils.getMergedAnnotation(
mergedConfig.getTestClass(), SpringApplicationTest.class);
propertySourceProperties.addAll(Arrays.asList(annotation.value()));
} }
} }
...@@ -23,27 +23,43 @@ import java.lang.annotation.Retention; ...@@ -23,27 +23,43 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; 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.core.env.Environment;
import org.springframework.test.context.BootstrapWith; 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 * therefore require full startup in the same way as a production application (listening
* on normal ports). Normally used in conjunction with * on normal ports. By default will load nested {@code @Configuration} classes, or
* {@code @SpringApplicationConfiguration}, * 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> * <p>
* This annotation can be used as an alternative to {@code @IntegrationTest} and * If you are not testing a web application consider using the
* {@code @WebAppConfiguration}. * {@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 * @author Phillip Webb
* @since 1.2.1 * @since 1.4.0
* @see org.springframework.boot.test.context.IntegrationTest * @see SpringApplicationTest
* @see IntegrationTest
*/ */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
@Inherited @Inherited
@Retention(RetentionPolicy.RUNTIME) @BootstrapWith(WebIntegrationTestContextBootstrapper.class)
@Target(ElementType.TYPE)
@BootstrapWith(WebAppIntegrationTestContextBootstrapper.class)
public @interface WebIntegrationTest { public @interface WebIntegrationTest {
/** /**
......
...@@ -16,11 +16,14 @@ ...@@ -16,11 +16,14 @@
package org.springframework.boot.test.context.web; 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.boot.test.context.SpringBootTestContextBootstrapper;
import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.test.context.MergedContextConfiguration; import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.TestContextBootstrapper; import org.springframework.test.context.TestContextBootstrapper;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.context.web.WebMergedContextConfiguration; import org.springframework.test.context.web.WebMergedContextConfiguration;
/** /**
...@@ -28,26 +31,36 @@ import org.springframework.test.context.web.WebMergedContextConfiguration; ...@@ -28,26 +31,36 @@ import org.springframework.test.context.web.WebMergedContextConfiguration;
* *
* @author Phillip Webb * @author Phillip Webb
*/ */
class WebAppIntegrationTestContextBootstrapper extends SpringBootTestContextBootstrapper { class WebIntegrationTestContextBootstrapper extends SpringBootTestContextBootstrapper {
@Override @Override
protected MergedContextConfiguration processMergedContextConfiguration( protected MergedContextConfiguration processMergedContextConfiguration(
MergedContextConfiguration mergedConfig) { MergedContextConfiguration mergedConfig) {
assertValidAnnotations(mergedConfig.getTestClass());
mergedConfig = super.processMergedContextConfiguration(mergedConfig); mergedConfig = super.processMergedContextConfiguration(mergedConfig);
WebIntegrationTest annotation = AnnotatedElementUtils.findMergedAnnotation( 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); mergedConfig.getTestClass(), WebIntegrationTest.class);
if (annotation != null) { propertySourceProperties.addAll(Arrays.asList(annotation.value()));
mergedConfig = new WebMergedContextConfiguration(mergedConfig, null); if (annotation.randomPort()) {
MergedContextConfigurationProperties properties = new MergedContextConfigurationProperties( propertySourceProperties.add("server.port=0");
mergedConfig);
if (annotation.randomPort()) {
properties.add(annotation.value(), "server.port:0");
}
else {
properties.add(annotation.value());
}
} }
return mergedConfig;
} }
} }
/*
* 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();
}
}
}
/* /*
* 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"); * 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.
...@@ -18,14 +18,17 @@ package org.springframework.boot.context.web; ...@@ -18,14 +18,17 @@ package org.springframework.boot.context.web;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationContextInitializer;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.WebApplicationContext;
/** /**
* {@link ApplicationContextInitializer} for setting the servlet context. * {@link ApplicationContextInitializer} for setting the servlet context.
* *
* @author Dave Syer * @author Dave Syer
* @author Phillip Webb
*/ */
public class ServletContextApplicationContextInitializer implements public class ServletContextApplicationContextInitializer implements
ApplicationContextInitializer<ConfigurableWebApplicationContext>, Ordered { ApplicationContextInitializer<ConfigurableWebApplicationContext>, Ordered {
...@@ -34,12 +37,27 @@ public class ServletContextApplicationContextInitializer implements ...@@ -34,12 +37,27 @@ public class ServletContextApplicationContextInitializer implements
private final ServletContext servletContext; private final ServletContext servletContext;
private final boolean addApplicationContextAttribute;
/** /**
* Create a new {@link ServletContextApplicationContextInitializer} instance. * Create a new {@link ServletContextApplicationContextInitializer} instance.
* @param servletContext the servlet that should be ultimately set. * @param servletContext the servlet that should be ultimately set.
*/ */
public ServletContextApplicationContextInitializer(ServletContext servletContext) { 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.servletContext = servletContext;
this.addApplicationContextAttribute = addApplicationContextAttribute;
} }
public void setOrder(int order) { public void setOrder(int order) {
...@@ -54,6 +72,12 @@ public class ServletContextApplicationContextInitializer implements ...@@ -54,6 +72,12 @@ public class ServletContextApplicationContextInitializer implements
@Override @Override
public void initialize(ConfigurableWebApplicationContext applicationContext) { public void initialize(ConfigurableWebApplicationContext applicationContext) {
applicationContext.setServletContext(this.servletContext); applicationContext.setServletContext(this.servletContext);
if (this.addApplicationContextAttribute) {
this.servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
applicationContext);
}
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment