diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplate.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplate.java index 4e47fffa32..2a5ebc994d 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplate.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -65,7 +65,7 @@ import org.springframework.web.util.UriTemplateHandler; * Apache Http Client 4.3.2 or better is available (recommended) it will be used as the * client, and by default configured to ignore cookies and redirects. *

- * Note: To prevent injection problems this class internally does not extend + * Note: To prevent injection problems this class intentionally does not extend * {@link RestTemplate}. If you need access to the underlying {@link RestTemplate} use * {@link #getRestTemplate()}. *

diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextCustomizer.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplateTestContextCustomizer.java similarity index 66% rename from spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextCustomizer.java rename to spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplateTestContextCustomizer.java index 63cc4992f7..205029345a 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextCustomizer.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplateTestContextCustomizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -14,22 +14,29 @@ * limitations under the License. */ -package org.springframework.boot.test.context; +package org.springframework.boot.test.web.client; import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.boot.test.web.client.LocalHostUriTemplateHandler; -import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate.HttpClientOption; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.ConfigurationClassPostProcessor; +import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.test.context.ContextCustomizer; import org.springframework.test.context.MergedContextConfiguration; @@ -40,7 +47,7 @@ import org.springframework.test.context.MergedContextConfiguration; * @author Phillip Webb * @author Andy Wilkinson */ -class SpringBootTestContextCustomizer implements ContextCustomizer { +class TestRestTemplateTestContextCustomizer implements ContextCustomizer { @Override public void customizeContext(ConfigurableApplicationContext context, @@ -61,8 +68,11 @@ class SpringBootTestContextCustomizer implements ContextCustomizer { private void registerTestRestTemplate(ConfigurableApplicationContext context, BeanDefinitionRegistry registry) { - registry.registerBeanDefinition(TestRestTemplate.class.getName(), - new RootBeanDefinition(TestRestTemplateFactory.class)); + RootBeanDefinition definition = new RootBeanDefinition( + TestRestTemplateRegistrar.class); + definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + registry.registerBeanDefinition(TestRestTemplateRegistrar.class.getName(), + definition); } @Override @@ -78,6 +88,45 @@ class SpringBootTestContextCustomizer implements ContextCustomizer { return true; } + /** + * {@link BeanDefinitionRegistryPostProcessor} that runs after the + * {@link ConfigurationClassPostProcessor} and add a {@link TestRestTemplateFactory} + * bean definition when a {@link TestRestTemplate} hasn't already been registered. + */ + private static class TestRestTemplateRegistrar + implements BeanDefinitionRegistryPostProcessor, Ordered, BeanFactoryAware { + + private BeanFactory beanFactory; + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE; + } + + @Override + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) + throws BeansException { + if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors( + (ListableBeanFactory) this.beanFactory, + TestRestTemplate.class).length == 0) { + registry.registerBeanDefinition(TestRestTemplate.class.getName(), + new RootBeanDefinition(TestRestTemplateFactory.class)); + } + + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) + throws BeansException { + } + + } + /** * {@link FactoryBean} used to create and configure a {@link TestRestTemplate}. */ @@ -88,7 +137,7 @@ class SpringBootTestContextCustomizer implements ContextCustomizer { private static final HttpClientOption[] SSL_OPTIONS = { HttpClientOption.SSL }; - private TestRestTemplate object; + private TestRestTemplate template; @Override public void setApplicationContext(ApplicationContext applicationContext) @@ -100,7 +149,7 @@ class SpringBootTestContextCustomizer implements ContextCustomizer { LocalHostUriTemplateHandler handler = new LocalHostUriTemplateHandler( applicationContext.getEnvironment(), sslEnabled ? "https" : "http"); template.setUriTemplateHandler(handler); - this.object = template; + this.template = template; } private boolean isSslEnabled(ApplicationContext context) { @@ -137,7 +186,7 @@ class SpringBootTestContextCustomizer implements ContextCustomizer { @Override public TestRestTemplate getObject() throws Exception { - return this.object; + return this.template; } } diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextCustomizerFactory.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplateTestContextCustomizerFactory.java similarity index 77% rename from spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextCustomizerFactory.java rename to spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplateTestContextCustomizerFactory.java index 60d2265a69..717f84f8dc 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextCustomizerFactory.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplateTestContextCustomizerFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -14,10 +14,11 @@ * limitations under the License. */ -package org.springframework.boot.test.context; +package org.springframework.boot.test.web.client; import java.util.List; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.test.context.ContextConfigurationAttributes; import org.springframework.test.context.ContextCustomizer; @@ -27,16 +28,16 @@ import org.springframework.test.context.ContextCustomizerFactory; * {@link ContextCustomizerFactory} for {@link SpringBootTest}. * * @author Andy Wilkinson - * @see SpringBootTestContextCustomizer + * @see TestRestTemplateTestContextCustomizer */ -class SpringBootTestContextCustomizerFactory implements ContextCustomizerFactory { +class TestRestTemplateTestContextCustomizerFactory implements ContextCustomizerFactory { @Override public ContextCustomizer createContextCustomizer(Class testClass, List configAttributes) { if (AnnotatedElementUtils.findMergedAnnotation(testClass, SpringBootTest.class) != null) { - return new SpringBootTestContextCustomizer(); + return new TestRestTemplateTestContextCustomizer(); } return null; } diff --git a/spring-boot-project/spring-boot-test/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-test/src/main/resources/META-INF/spring.factories index 96e7abd05c..1007e0aa49 100644 --- a/spring-boot-project/spring-boot-test/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-test/src/main/resources/META-INF/spring.factories @@ -1,10 +1,10 @@ # Spring Test ContextCustomizerFactories org.springframework.test.context.ContextCustomizerFactory=\ org.springframework.boot.test.context.ImportsContextCustomizerFactory,\ -org.springframework.boot.test.context.SpringBootTestContextCustomizerFactory,\ org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizerFactory,\ org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory,\ org.springframework.boot.test.mock.mockito.MockitoContextCustomizerFactory,\ +org.springframework.boot.test.web.client.TestRestTemplateTestContextCustomizerFactory,\ org.springframework.boot.test.web.reactive.WebTestClientContextCustomizerFactory # Test Execution Listeners diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/NoTestRestTemplateBeanChecker.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/NoTestRestTemplateBeanChecker.java new file mode 100644 index 0000000000..b57925c767 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/NoTestRestTemplateBeanChecker.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2018 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.web.client; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.type.AnnotationMetadata; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * {@link ImportSelector} to check no {@link TestRestTemplate} definition is registered + * when config classes are processed. + */ +class NoTestRestTemplateBeanChecker implements ImportSelector, BeanFactoryAware { + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + assertThat(BeanFactoryUtils.beanNamesForTypeIncludingAncestors( + (ListableBeanFactory) beanFactory, TestRestTemplate.class)).isEmpty(); + } + + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return new String[0]; + } + +} diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/TestRestTemplateTestContextCustomizerIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/TestRestTemplateTestContextCustomizerIntegrationTests.java new file mode 100644 index 0000000000..2cd5785d32 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/TestRestTemplateTestContextCustomizerIntegrationTests.java @@ -0,0 +1,83 @@ +/* + * Copyright 2012-2018 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.web.client; + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.GenericServlet; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration test for {@link TestRestTemplateTestContextCustomizer}. + * + * @author Phillip Webb + */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@DirtiesContext +public class TestRestTemplateTestContextCustomizerIntegrationTests { + + @Autowired + private TestRestTemplate restTemplate; + + @Test + public void test() { + assertThat(this.restTemplate.getForObject("/", String.class)).contains("hello"); + } + + @Configuration + @Import({ TestServlet.class, NoTestRestTemplateBeanChecker.class }) + static class TestConfig { + + @Bean + public TomcatServletWebServerFactory webServerFactory() { + return new TomcatServletWebServerFactory(0); + } + + } + + static class TestServlet extends GenericServlet { + + @Override + public void service(ServletRequest request, ServletResponse response) + throws ServletException, IOException { + try (PrintWriter writer = response.getWriter()) { + writer.println("hello"); + } + } + + } + +} diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/TestRestTemplateTestContextCustomizerWithOverrideIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/TestRestTemplateTestContextCustomizerWithOverrideIntegrationTests.java new file mode 100644 index 0000000000..b03bb7dfd3 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/TestRestTemplateTestContextCustomizerWithOverrideIntegrationTests.java @@ -0,0 +1,93 @@ +/* + * Copyright 2012-2018 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.web.client; + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.GenericServlet; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration test for {@link TestRestTemplateTestContextCustomizer} with a custom + * {@link TestRestTemplate} bean. + * + * @author Phillip Webb + */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@DirtiesContext +public class TestRestTemplateTestContextCustomizerWithOverrideIntegrationTests { + + @Autowired + private TestRestTemplate restTemplate; + + @Test + public void test() { + assertThat(this.restTemplate).isInstanceOf(CustomTestRestTemplate.class); + } + + @Configuration + @Import({ TestServlet.class, NoTestRestTemplateBeanChecker.class }) + static class TestConfig { + + @Bean + public TomcatServletWebServerFactory webServerFactory() { + return new TomcatServletWebServerFactory(0); + } + + @Bean + public TestRestTemplate template() { + return new CustomTestRestTemplate(); + } + + } + + static class TestServlet extends GenericServlet { + + @Override + public void service(ServletRequest request, ServletResponse response) + throws ServletException, IOException { + try (PrintWriter writer = response.getWriter()) { + writer.println("hello"); + } + } + + } + + static class CustomTestRestTemplate extends TestRestTemplate { + + } + +}