From 73a2a4b3833bebf0bb703e218ad277d269b01cc0 Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Wed, 6 Jan 2021 12:32:14 -0800 Subject: [PATCH] Consider context-path for WebTestClient baseUrl Fixes gh-24168 --- .../WebTestClientContextCustomizer.java | 48 +++++++++- ...textCustomizerWithCustomBasePathTests.java | 87 +++++++++++++++++++ ...tCustomizerWithCustomContextPathTests.java | 79 +++++++++++++++++ 3 files changed, 212 insertions(+), 2 deletions(-) create mode 100644 spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizerWithCustomBasePathTests.java create mode 100644 spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizerWithCustomContextPathTests.java diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizer.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizer.java index 40bf66265264..daf5c90feabc 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/server/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-2019 the original author or authors. + * Copyright 2012-2021 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. @@ -30,6 +30,7 @@ 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.WebApplicationType; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.web.codec.CodecCustomizer; @@ -45,7 +46,10 @@ import org.springframework.test.context.ContextCustomizer; import org.springframework.test.context.MergedContextConfiguration; import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.context.WebApplicationContext; import org.springframework.web.reactive.function.client.ExchangeStrategies; /** @@ -132,6 +136,10 @@ public static class WebTestClientFactory implements FactoryBean, private WebTestClient object; + private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext"; + + private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext"; + @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; @@ -158,13 +166,49 @@ public WebTestClient getObject() throws Exception { private WebTestClient createWebTestClient() { boolean sslEnabled = isSslEnabled(this.applicationContext); String port = this.applicationContext.getEnvironment().getProperty("local.server.port", "8080"); - String baseUrl = (sslEnabled ? "https" : "http") + "://localhost:" + port; + String baseUrl = getBaseUrl(sslEnabled, port); WebTestClient.Builder builder = WebTestClient.bindToServer(); customizeWebTestClientBuilder(builder, this.applicationContext); customizeWebTestClientCodecs(builder, this.applicationContext); return builder.baseUrl(baseUrl).build(); } + private String getBaseUrl(boolean sslEnabled, String port) { + String basePath = deduceBasePath(); + String pathSegment = (StringUtils.hasText(basePath)) ? basePath : ""; + return (sslEnabled ? "https" : "http") + "://localhost:" + port + pathSegment; + } + + private String deduceBasePath() { + WebApplicationType webApplicationType = deduceFromApplicationContext(this.applicationContext.getClass()); + if (webApplicationType == WebApplicationType.REACTIVE) { + return this.applicationContext.getEnvironment().getProperty("spring.webflux.base-path"); + } + else if (webApplicationType == WebApplicationType.SERVLET) { + return ((WebApplicationContext) this.applicationContext).getServletContext().getContextPath(); + } + return null; + } + + static WebApplicationType deduceFromApplicationContext(Class applicationContextClass) { + if (isAssignable(SERVLET_APPLICATION_CONTEXT_CLASS, applicationContextClass)) { + return WebApplicationType.SERVLET; + } + if (isAssignable(REACTIVE_APPLICATION_CONTEXT_CLASS, applicationContextClass)) { + return WebApplicationType.REACTIVE; + } + return WebApplicationType.NONE; + } + + private static boolean isAssignable(String target, Class type) { + try { + return ClassUtils.resolveClassName(target, null).isAssignableFrom(type); + } + catch (Throwable ex) { + return false; + } + } + private boolean isSslEnabled(ApplicationContext context) { try { AbstractReactiveWebServerFactory webServerFactory = context diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizerWithCustomBasePathTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizerWithCustomBasePathTests.java new file mode 100644 index 000000000000..c1ef48bf3d21 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizerWithCustomBasePathTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2012-2021 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 + * + * https://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 java.util.Collections; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.buffer.DefaultDataBufferFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.ContextPathCompositeHandler; +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.context.TestPropertySource; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * Tests for {@link WebTestClientContextCustomizer} with a custom base path for a reactive + * web application. + * + * @author Madhura Bhave + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = "spring.main.web-application-type=reactive") +@TestPropertySource(properties = "spring.webflux.base-path=/test") +class WebTestClientContextCustomizerWithCustomBasePathTests { + + @Autowired + private WebTestClient webTestClient; + + @Test + void test() { + this.webTestClient.get().uri("/hello").exchange().expectBody(String.class).isEqualTo("hello world"); + } + + @Configuration(proxyBeanMethods = false) + static class TestConfig { + + @Bean + TomcatReactiveWebServerFactory webServerFactory() { + return new TomcatReactiveWebServerFactory(0); + } + + @Bean + HttpHandler httpHandler() { + TestHandler httpHandler = new TestHandler(); + Map handlersMap = Collections.singletonMap("/test", httpHandler); + return new ContextPathCompositeHandler(handlersMap); + } + + } + + 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 world".getBytes()))); + } + + } + +} diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizerWithCustomContextPathTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizerWithCustomContextPathTests.java new file mode 100644 index 000000000000..9df89b87520c --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizerWithCustomContextPathTests.java @@ -0,0 +1,79 @@ +/* + * Copyright 2012-2021 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 + * + * https://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.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +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.context.TestPropertySource; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.DispatcherServlet; + +/** + * Tests for {@link WebTestClientContextCustomizer} with a custom context path for a + * servlet web application. + * + * @author Madhura Bhave + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@TestPropertySource(properties = "server.servlet.context-path=/test") +class WebTestClientContextCustomizerWithCustomContextPathTests { + + @Autowired + private WebTestClient webTestClient; + + @Test + void test() { + this.webTestClient.get().uri("/hello").exchange().expectBody(String.class).isEqualTo("hello world"); + } + + @Configuration(proxyBeanMethods = false) + @Import(TestController.class) + static class TestConfig { + + @Bean + TomcatServletWebServerFactory webServerFactory() { + TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(0); + factory.setContextPath("/test"); + return factory; + } + + @Bean + DispatcherServlet dispatcherServlet() { + return new DispatcherServlet(); + } + + } + + @RestController + static class TestController { + + @GetMapping("/hello") + String hello() { + return "hello world"; + } + + } + +}