diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/IntegrationTestPropertiesListener.java b/spring-boot-test/src/main/java/org/springframework/boot/test/IntegrationTestPropertiesListener.java index 9eb7f8c0ec..39b211e3b7 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/IntegrationTestPropertiesListener.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/IntegrationTestPropertiesListener.java @@ -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; + } } diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/context/MergedContextConfigurationProperties.java b/spring-boot-test/src/main/java/org/springframework/boot/test/MergedContextConfigurationProperties.java similarity index 88% rename from spring-boot-test/src/main/java/org/springframework/boot/test/context/MergedContextConfigurationProperties.java rename to spring-boot-test/src/main/java/org/springframework/boot/test/MergedContextConfigurationProperties.java index c55338c21c..a2d44a5978 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/context/MergedContextConfigurationProperties.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/MergedContextConfigurationProperties.java @@ -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; } diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/SpringApplicationContextLoader.java b/spring-boot-test/src/main/java/org/springframework/boot/test/SpringApplicationContextLoader.java index c22d9af9ac..85a9ac6424 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/SpringApplicationContextLoader.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/SpringApplicationContextLoader.java @@ -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 diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/context/IntegrationTest.java b/spring-boot-test/src/main/java/org/springframework/boot/test/context/IntegrationTest.java index bed0ab54f9..9fd0d43cf3 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/context/IntegrationTest.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/context/IntegrationTest.java @@ -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. *

- * 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 { /** diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/context/IntegrationTestContextBootstrapper.java b/spring-boot-test/src/main/java/org/springframework/boot/test/context/IntegrationTestContextBootstrapper.java new file mode 100644 index 0000000000..23d2595806 --- /dev/null +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/context/IntegrationTestContextBootstrapper.java @@ -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 getDefaultTestExecutionListenerClassNames() { + // Remove the ServletTestExecutionListener because it only deals with MockServlet + List classNames = new ArrayList( + 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 propertySourceProperties) { + IntegrationTest annotation = AnnotatedElementUtils + .getMergedAnnotation(mergedConfig.getTestClass(), IntegrationTest.class); + propertySourceProperties.addAll(Arrays.asList(annotation.value())); + } + +} diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/context/IntegrationTestPropertiesListener.java b/spring-boot-test/src/main/java/org/springframework/boot/test/context/IntegrationTestPropertiesListener.java deleted file mode 100644 index a405ed57f6..0000000000 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/context/IntegrationTestPropertiesListener.java +++ /dev/null @@ -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; - } - -} diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringApplicationConfiguration.java b/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringApplicationConfiguration.java index 95ea77bba1..83c320b03c 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringApplicationConfiguration.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringApplicationConfiguration.java @@ -34,9 +34,20 @@ import org.springframework.test.context.ContextConfiguration; *

* Similar to the standard {@link ContextConfiguration @ContextConfiguration} but uses * Spring Boot's {@link SpringApplicationContextLoader}. + *

+ * 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 */ diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringApplicationContextLoader.java b/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringApplicationContextLoader.java index 704503d072..7787ea92eb 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringApplicationContextLoader.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringApplicationContextLoader.java @@ -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. - *

- * 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: + *

+ * 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(...)}). *

* 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 INTEGRATION_TEST_ANNOTATIONS; + + static { + Set annotations = new LinkedHashSet(); + 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> 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 WEB_CONTEXT_CLASS = GenericWebApplicationContext.class; + private final boolean integrationTest; + + WebConfigurer(boolean integrationTest) { + this.integrationTest = integrationTest; + } + void configure(MergedContextConfiguration configuration, SpringApplication application, List> 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 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}. diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringApplicationTest.java b/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringApplicationTest.java new file mode 100644 index 0000000000..a2333f6e06 --- /dev/null +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringApplicationTest.java @@ -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. + *

+ * 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 {}; + +} diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringApplicationTestContextBootstrapper.java b/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringApplicationTestContextBootstrapper.java new file mode 100644 index 0000000000..64eba434dd --- /dev/null +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringApplicationTestContextBootstrapper.java @@ -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 propertySourceProperties) { + SpringApplicationTest annotation = AnnotatedElementUtils.getMergedAnnotation( + mergedConfig.getTestClass(), SpringApplicationTest.class); + propertySourceProperties.addAll(Arrays.asList(annotation.value())); + } + +} diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/context/web/WebIntegrationTest.java b/spring-boot-test/src/main/java/org/springframework/boot/test/context/web/WebIntegrationTest.java index 33b68f8e07..2303e9d0ce 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/context/web/WebIntegrationTest.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/context/web/WebIntegrationTest.java @@ -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. *

- * 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 { /** diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/context/web/WebAppIntegrationTestContextBootstrapper.java b/spring-boot-test/src/main/java/org/springframework/boot/test/context/web/WebIntegrationTestContextBootstrapper.java similarity index 56% rename from spring-boot-test/src/main/java/org/springframework/boot/test/context/web/WebAppIntegrationTestContextBootstrapper.java rename to spring-boot-test/src/main/java/org/springframework/boot/test/context/web/WebIntegrationTestContextBootstrapper.java index 3356146499..1abdd39218 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/context/web/WebAppIntegrationTestContextBootstrapper.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/context/web/WebIntegrationTestContextBootstrapper.java @@ -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( + 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 propertySourceProperties) { + WebIntegrationTest annotation = AnnotatedElementUtils.getMergedAnnotation( 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()); - } + propertySourceProperties.addAll(Arrays.asList(annotation.value())); + if (annotation.randomPort()) { + propertySourceProperties.add("server.port=0"); } - return mergedConfig; } } diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/context/SpringApplicationTestTests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/context/SpringApplicationTestTests.java new file mode 100644 index 0000000000..2b65fe5c3c --- /dev/null +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/context/SpringApplicationTestTests.java @@ -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(); + } + + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/context/web/ServletContextApplicationContextInitializer.java b/spring-boot/src/main/java/org/springframework/boot/context/web/ServletContextApplicationContextInitializer.java index bbc47c21bf..11c5a8c956 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/web/ServletContextApplicationContextInitializer.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/web/ServletContextApplicationContextInitializer.java @@ -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, 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); + } + } }