Skip to content

Commit 73a2a4b

Browse files
committed
Consider context-path for WebTestClient baseUrl
Fixes gh-24168
1 parent 52e47c4 commit 73a2a4b

File tree

3 files changed

+212
-2
lines changed

3 files changed

+212
-2
lines changed

spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizer.java

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@
3030
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
3131
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
3232
import org.springframework.beans.factory.support.RootBeanDefinition;
33+
import org.springframework.boot.WebApplicationType;
3334
import org.springframework.boot.test.context.SpringBootTest;
3435
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
3536
import org.springframework.boot.web.codec.CodecCustomizer;
@@ -45,7 +46,10 @@
4546
import org.springframework.test.context.ContextCustomizer;
4647
import org.springframework.test.context.MergedContextConfiguration;
4748
import org.springframework.test.web.reactive.server.WebTestClient;
49+
import org.springframework.util.ClassUtils;
4850
import org.springframework.util.CollectionUtils;
51+
import org.springframework.util.StringUtils;
52+
import org.springframework.web.context.WebApplicationContext;
4953
import org.springframework.web.reactive.function.client.ExchangeStrategies;
5054

5155
/**
@@ -132,6 +136,10 @@ public static class WebTestClientFactory implements FactoryBean<WebTestClient>,
132136

133137
private WebTestClient object;
134138

139+
private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";
140+
141+
private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";
142+
135143
@Override
136144
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
137145
this.applicationContext = applicationContext;
@@ -158,13 +166,49 @@ public WebTestClient getObject() throws Exception {
158166
private WebTestClient createWebTestClient() {
159167
boolean sslEnabled = isSslEnabled(this.applicationContext);
160168
String port = this.applicationContext.getEnvironment().getProperty("local.server.port", "8080");
161-
String baseUrl = (sslEnabled ? "https" : "http") + "://localhost:" + port;
169+
String baseUrl = getBaseUrl(sslEnabled, port);
162170
WebTestClient.Builder builder = WebTestClient.bindToServer();
163171
customizeWebTestClientBuilder(builder, this.applicationContext);
164172
customizeWebTestClientCodecs(builder, this.applicationContext);
165173
return builder.baseUrl(baseUrl).build();
166174
}
167175

176+
private String getBaseUrl(boolean sslEnabled, String port) {
177+
String basePath = deduceBasePath();
178+
String pathSegment = (StringUtils.hasText(basePath)) ? basePath : "";
179+
return (sslEnabled ? "https" : "http") + "://localhost:" + port + pathSegment;
180+
}
181+
182+
private String deduceBasePath() {
183+
WebApplicationType webApplicationType = deduceFromApplicationContext(this.applicationContext.getClass());
184+
if (webApplicationType == WebApplicationType.REACTIVE) {
185+
return this.applicationContext.getEnvironment().getProperty("spring.webflux.base-path");
186+
}
187+
else if (webApplicationType == WebApplicationType.SERVLET) {
188+
return ((WebApplicationContext) this.applicationContext).getServletContext().getContextPath();
189+
}
190+
return null;
191+
}
192+
193+
static WebApplicationType deduceFromApplicationContext(Class<?> applicationContextClass) {
194+
if (isAssignable(SERVLET_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
195+
return WebApplicationType.SERVLET;
196+
}
197+
if (isAssignable(REACTIVE_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
198+
return WebApplicationType.REACTIVE;
199+
}
200+
return WebApplicationType.NONE;
201+
}
202+
203+
private static boolean isAssignable(String target, Class<?> type) {
204+
try {
205+
return ClassUtils.resolveClassName(target, null).isAssignableFrom(type);
206+
}
207+
catch (Throwable ex) {
208+
return false;
209+
}
210+
}
211+
168212
private boolean isSslEnabled(ApplicationContext context) {
169213
try {
170214
AbstractReactiveWebServerFactory webServerFactory = context
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright 2012-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.test.web.reactive.server;
18+
19+
import java.util.Collections;
20+
import java.util.Map;
21+
22+
import org.junit.jupiter.api.Test;
23+
import reactor.core.publisher.Mono;
24+
25+
import org.springframework.beans.factory.annotation.Autowired;
26+
import org.springframework.boot.test.context.SpringBootTest;
27+
import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory;
28+
import org.springframework.context.annotation.Bean;
29+
import org.springframework.context.annotation.Configuration;
30+
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
31+
import org.springframework.http.HttpStatus;
32+
import org.springframework.http.server.reactive.ContextPathCompositeHandler;
33+
import org.springframework.http.server.reactive.HttpHandler;
34+
import org.springframework.http.server.reactive.ServerHttpRequest;
35+
import org.springframework.http.server.reactive.ServerHttpResponse;
36+
import org.springframework.test.context.TestPropertySource;
37+
import org.springframework.test.web.reactive.server.WebTestClient;
38+
39+
/**
40+
* Tests for {@link WebTestClientContextCustomizer} with a custom base path for a reactive
41+
* web application.
42+
*
43+
* @author Madhura Bhave
44+
*/
45+
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
46+
properties = "spring.main.web-application-type=reactive")
47+
@TestPropertySource(properties = "spring.webflux.base-path=/test")
48+
class WebTestClientContextCustomizerWithCustomBasePathTests {
49+
50+
@Autowired
51+
private WebTestClient webTestClient;
52+
53+
@Test
54+
void test() {
55+
this.webTestClient.get().uri("/hello").exchange().expectBody(String.class).isEqualTo("hello world");
56+
}
57+
58+
@Configuration(proxyBeanMethods = false)
59+
static class TestConfig {
60+
61+
@Bean
62+
TomcatReactiveWebServerFactory webServerFactory() {
63+
return new TomcatReactiveWebServerFactory(0);
64+
}
65+
66+
@Bean
67+
HttpHandler httpHandler() {
68+
TestHandler httpHandler = new TestHandler();
69+
Map<String, HttpHandler> handlersMap = Collections.singletonMap("/test", httpHandler);
70+
return new ContextPathCompositeHandler(handlersMap);
71+
}
72+
73+
}
74+
75+
static class TestHandler implements HttpHandler {
76+
77+
private static final DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
78+
79+
@Override
80+
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
81+
response.setStatusCode(HttpStatus.OK);
82+
return response.writeWith(Mono.just(factory.wrap("hello world".getBytes())));
83+
}
84+
85+
}
86+
87+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright 2012-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.test.web.reactive.server;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import org.springframework.beans.factory.annotation.Autowired;
22+
import org.springframework.boot.test.context.SpringBootTest;
23+
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
24+
import org.springframework.context.annotation.Bean;
25+
import org.springframework.context.annotation.Configuration;
26+
import org.springframework.context.annotation.Import;
27+
import org.springframework.test.context.TestPropertySource;
28+
import org.springframework.test.web.reactive.server.WebTestClient;
29+
import org.springframework.web.bind.annotation.GetMapping;
30+
import org.springframework.web.bind.annotation.RestController;
31+
import org.springframework.web.servlet.DispatcherServlet;
32+
33+
/**
34+
* Tests for {@link WebTestClientContextCustomizer} with a custom context path for a
35+
* servlet web application.
36+
*
37+
* @author Madhura Bhave
38+
*/
39+
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
40+
@TestPropertySource(properties = "server.servlet.context-path=/test")
41+
class WebTestClientContextCustomizerWithCustomContextPathTests {
42+
43+
@Autowired
44+
private WebTestClient webTestClient;
45+
46+
@Test
47+
void test() {
48+
this.webTestClient.get().uri("/hello").exchange().expectBody(String.class).isEqualTo("hello world");
49+
}
50+
51+
@Configuration(proxyBeanMethods = false)
52+
@Import(TestController.class)
53+
static class TestConfig {
54+
55+
@Bean
56+
TomcatServletWebServerFactory webServerFactory() {
57+
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(0);
58+
factory.setContextPath("/test");
59+
return factory;
60+
}
61+
62+
@Bean
63+
DispatcherServlet dispatcherServlet() {
64+
return new DispatcherServlet();
65+
}
66+
67+
}
68+
69+
@RestController
70+
static class TestController {
71+
72+
@GetMapping("/hello")
73+
String hello() {
74+
return "hello world";
75+
}
76+
77+
}
78+
79+
}

0 commit comments

Comments
 (0)