From b7c52da457bed3295e02ebc64dca63820d77ab74 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 24 Jan 2018 23:24:13 -0800 Subject: [PATCH] Allow custom WebTestClient beans Update `@SpringBootTest` `WebTestClient` support so that the bean definition is only registered when the user has not defined or auto-configured their own. See gh-10556 --- .../boot/test/context/SpringBootTest.java | 7 +- .../WebTestClientContextCustomizer.java | 58 +++++++++++- ...WebTestClientContextCustomizerFactory.java | 6 +- .../web/reactive/server/package-info.java | 21 +++++ .../main/resources/META-INF/spring.factories | 2 +- .../server/NoWebTestClientBeanChecker.java | 47 ++++++++++ ...ientContextCustomizerIntegrationTests.java | 81 ++++++++++++++++ ...ustomizerWithOverrideIntegrationTests.java | 93 +++++++++++++++++++ 8 files changed, 304 insertions(+), 11 deletions(-) rename spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/{ => server}/WebTestClientContextCustomizer.java (71%) rename spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/{ => server}/WebTestClientContextCustomizerFactory.java (89%) create mode 100644 spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/server/package-info.java create mode 100644 spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/reactive/server/NoWebTestClientBeanChecker.java create mode 100644 spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizerIntegrationTests.java create mode 100644 spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizerWithOverrideIntegrationTests.java diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTest.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTest.java index 66701d2b1a..736c439ec1 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTest.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTest.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. @@ -57,8 +57,9 @@ import org.springframework.web.context.WebApplicationContext; * {@link WebEnvironment#DEFINED_PORT defined} or {@link WebEnvironment#RANDOM_PORT * random} port. *
  • Registers a {@link org.springframework.boot.test.web.client.TestRestTemplate - * TestRestTemplate} bean for use in web tests that are using a fully running web server. - *
  • + * TestRestTemplate} and/or + * {@link org.springframework.test.web.reactive.server.WebTestClient WebTestClient} bean + * for use in web tests that are using a fully running web server. * * * @author Phillip Webb diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/WebTestClientContextCustomizer.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizer.java similarity index 71% rename from spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/WebTestClientContextCustomizer.java rename to spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizer.java index 3279677dea..a7550cec26 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/WebTestClientContextCustomizer.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizer.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,15 +14,21 @@ * limitations under the License. */ -package org.springframework.boot.test.web.reactive; +package org.springframework.boot.test.web.reactive.server; import java.util.Collection; 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.context.SpringBootTest; import org.springframework.boot.web.codec.CodecCustomizer; @@ -30,6 +36,8 @@ import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFac 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; @@ -63,8 +71,11 @@ class WebTestClientContextCustomizer implements ContextCustomizer { private void registerWebTestClient(ConfigurableApplicationContext context, BeanDefinitionRegistry registry) { - registry.registerBeanDefinition(WebTestClient.class.getName(), - new RootBeanDefinition(WebTestClientFactory.class)); + RootBeanDefinition definition = new RootBeanDefinition( + WebTestClientRegistrar.class); + definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + registry.registerBeanDefinition(WebTestClientRegistrar.class.getName(), + definition); } @Override @@ -77,6 +88,45 @@ class WebTestClientContextCustomizer implements ContextCustomizer { return (obj != null && obj.getClass() == getClass()); } + /** + * {@link BeanDefinitionRegistryPostProcessor} that runs after the + * {@link ConfigurationClassPostProcessor} and add a {@link WebTestClientFactory} bean + * definition when a {@link WebTestClient} hasn't already been registered. + */ + private static class WebTestClientRegistrar + 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, + WebTestClient.class).length == 0) { + registry.registerBeanDefinition(WebTestClient.class.getName(), + new RootBeanDefinition(WebTestClientFactory.class)); + } + + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) + throws BeansException { + } + + } + /** * {@link FactoryBean} used to create and configure a {@link WebTestClient}. */ diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/WebTestClientContextCustomizerFactory.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizerFactory.java similarity index 89% rename from spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/WebTestClientContextCustomizerFactory.java rename to spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizerFactory.java index 6558ce2a71..c9e4a22f18 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/WebTestClientContextCustomizerFactory.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizerFactory.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,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.test.web.reactive; +package org.springframework.boot.test.web.reactive.server; import java.util.List; @@ -30,7 +30,7 @@ import org.springframework.util.ClassUtils; * * @author Stephane Nicoll */ -public class WebTestClientContextCustomizerFactory implements ContextCustomizerFactory { +class WebTestClientContextCustomizerFactory implements ContextCustomizerFactory { private static final String WEB_TEST_CLIENT_CLASS = "org.springframework.web.reactive.function.client.WebClient"; diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/server/package-info.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/server/package-info.java new file mode 100644 index 0000000000..2581f9a4c3 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/server/package-info.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +/** + * Spring Boot support for testing Spring WebFlux server endpoints via + * {@link org.springframework.test.web.reactive.server.WebTestClient}. + */ +package org.springframework.boot.test.web.reactive.server; 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 1007e0aa49..aec403293e 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 @@ -5,7 +5,7 @@ org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizerFacto 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 +org.springframework.boot.test.web.reactive.server.WebTestClientContextCustomizerFactory # Test Execution Listeners org.springframework.test.context.TestExecutionListener=\ diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/reactive/server/NoWebTestClientBeanChecker.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/reactive/server/NoWebTestClientBeanChecker.java new file mode 100644 index 0000000000..b25f1ba71c --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/reactive/server/NoWebTestClientBeanChecker.java @@ -0,0 +1,47 @@ +/* + * 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.reactive.server; + +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 org.springframework.test.web.reactive.server.WebTestClient; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * {@link ImportSelector} to check no {@link WebTestClient} definition is registered when + * config classes are processed. + */ +class NoWebTestClientBeanChecker implements ImportSelector, BeanFactoryAware { + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + assertThat(BeanFactoryUtils.beanNamesForTypeIncludingAncestors( + (ListableBeanFactory) beanFactory, WebTestClient.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/reactive/server/WebTestClientContextCustomizerIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizerIntegrationTests.java new file mode 100644 index 0000000000..708c80aacd --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizerIntegrationTests.java @@ -0,0 +1,81 @@ +/* + * 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.reactive.server; + +import org.junit.Test; +import org.junit.runner.RunWith; +import reactor.core.publisher.Mono; + +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.TomcatReactiveWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.io.buffer.DefaultDataBufferFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.HttpHandler; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * Integration test for {@link WebTestClientContextCustomizer}. + * + * @author Phillip Webb + */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = "spring.main.web-application-type=reactive") +@DirtiesContext +public class WebTestClientContextCustomizerIntegrationTests { + + @Autowired + private WebTestClient webTestClient; + + @Test + public void test() { + this.webTestClient.get().uri("/").exchange().expectBody(String.class) + .isEqualTo("hello"); + } + + @Configuration + @Import({ TestHandler.class, NoWebTestClientBeanChecker.class }) + static class TestConfig { + + @Bean + public TomcatReactiveWebServerFactory webServerFactory() { + return new TomcatReactiveWebServerFactory(0); + } + + } + + static class TestHandler implements HttpHandler { + + private static final DefaultDataBufferFactory factory = new DefaultDataBufferFactory(); + + @Override + public Mono handle(ServerHttpRequest request, ServerHttpResponse response) { + response.setStatusCode(HttpStatus.OK); + return response.writeWith(Mono.just(factory.wrap("hello".getBytes()))); + } + + } + +} diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizerWithOverrideIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizerWithOverrideIntegrationTests.java new file mode 100644 index 0000000000..943c616a16 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizerWithOverrideIntegrationTests.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.reactive.server; + +import org.junit.Test; +import org.junit.runner.RunWith; +import reactor.core.publisher.Mono; + +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.TomcatReactiveWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.io.buffer.DefaultDataBufferFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.HttpHandler; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.reactive.server.WebTestClient; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Integration test for {@link WebTestClientContextCustomizer} with a custom + * {@link WebTestClient} bean. + * + * @author Phillip Webb + */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = "spring.main.web-application-type=reactive") +@DirtiesContext +public class WebTestClientContextCustomizerWithOverrideIntegrationTests { + + @Autowired + private WebTestClient webTestClient; + + @Test + public void test() { + assertThat(this.webTestClient).isInstanceOf(CustomWebTestClient.class); + } + + @Configuration + @Import({ TestHandler.class, NoWebTestClientBeanChecker.class }) + static class TestConfig { + + @Bean + public TomcatReactiveWebServerFactory webServerFactory() { + return new TomcatReactiveWebServerFactory(0); + } + + @Bean + public WebTestClient webTestClient() { + return mock(CustomWebTestClient.class); + } + + } + + static class TestHandler implements HttpHandler { + + private static final DefaultDataBufferFactory factory = new DefaultDataBufferFactory(); + + @Override + public Mono handle(ServerHttpRequest request, ServerHttpResponse response) { + response.setStatusCode(HttpStatus.OK); + return response.writeWith(Mono.just(factory.wrap("hello".getBytes()))); + } + + } + + interface CustomWebTestClient extends WebTestClient { + + } + +}