Skip to content

Commit 879b7e6

Browse files
committed
Bind WebTestClient to the default WebHandler
Previously, if multiple WebHandler beans were present, the auto-config for WebTestClient fail to identify a suitable candidate as it expects to only have such a bean. This commit updates the logic to look for a well-known bean name that WebFlux uses, and clarify the exception message to state that a bean with a given name is expected to be found. The exception message has been further refined to mention that, if such a bean is not present, then a MockMVc-compatible ApplicationContext should be available (i.e. WebApplicationContext). Closes gh-47617
1 parent c4d3583 commit 879b7e6

4 files changed

Lines changed: 163 additions & 62 deletions

File tree

module/spring-boot-webtestclient/src/main/java/org/springframework/boot/webtestclient/WebTestClientAutoConfiguration.java

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import java.util.List;
2020

21-
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
2221
import org.springframework.beans.factory.ObjectProvider;
2322
import org.springframework.boot.autoconfigure.AutoConfiguration;
2423
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -40,7 +39,7 @@
4039
import org.springframework.util.ClassUtils;
4140
import org.springframework.web.context.WebApplicationContext;
4241
import org.springframework.web.reactive.function.client.WebClient;
43-
import org.springframework.web.server.WebHandler;
42+
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
4443

4544
/**
4645
* Auto-configuration for {@link WebTestClient}.
@@ -79,31 +78,22 @@ private WebTestClient.Builder getBuilder(ApplicationContext applicationContext,
7978
if (baseUrl != null) {
8079
return WebTestClient.bindToServer().uriBuilderFactory(BaseUrlUriBuilderFactory.get(baseUrl));
8180
}
82-
if (hasBean(applicationContext, WebHandler.class)) {
81+
if (applicationContext.containsBean(WebHttpHandlerBuilder.WEB_HANDLER_BEAN_NAME)) {
8382
MockServerSpec<?> spec = WebTestClient.bindToApplicationContext(applicationContext);
8483
configurers.forEach(spec::apply);
8584
return spec.configureClient();
8685
}
8786
if (ClassUtils.isPresent(WEB_APPLICATION_CONTEXT_CLASS, applicationContext.getClassLoader())) {
88-
if (hasBean(applicationContext, MockMvc.class)) {
89-
return MockMvcWebTestClient.bindTo(applicationContext.getBean(MockMvc.class));
87+
MockMvc mockMvc = applicationContext.getBeanProvider(MockMvc.class).getIfUnique();
88+
if (mockMvc != null) {
89+
return MockMvcWebTestClient.bindTo(mockMvc);
9090
}
9191
if (applicationContext instanceof WebApplicationContext webApplicationContext) {
9292
return MockMvcWebTestClient.bindToApplicationContext(webApplicationContext).configureClient();
9393
}
9494
}
9595
throw new IllegalStateException(
96-
"Mock WebTestClient support requires a WebHandler or MockMvc bean and neither was present");
97-
}
98-
99-
private boolean hasBean(ApplicationContext applicationContext, Class<?> type) {
100-
try {
101-
applicationContext.getBean(type);
102-
return true;
103-
}
104-
catch (NoSuchBeanDefinitionException ex) {
105-
return false;
106-
}
96+
"Mock WebTestClient support requires a WebHandler (named 'webHandler') bean or a WebApplicationContext and neither was present");
10797
}
10898

10999
}

module/spring-boot-webtestclient/src/test/java/org/springframework/boot/webflux/test/autoconfigure/WebTestClientAutoConfigurationTests.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,31 @@ void shouldDefineWebTestClientBoundToHttpHandler() {
7171
});
7272
}
7373

74+
@Test
75+
void shouldFailWhenDefaultWebHandlerIsNotAvailable() {
76+
this.contextRunner.withBean("myWebHandler", WebHandler.class, () -> mock(WebHandler.class)).run((context) -> {
77+
assertThat(context).hasFailed();
78+
assertThat(context).getFailure()
79+
.rootCause()
80+
.isInstanceOf(RuntimeException.class)
81+
.hasMessageStartingWith("Mock WebTestClient support requires")
82+
.hasMessageContaining("WebHandler");
83+
});
84+
}
85+
86+
@Test
87+
void shouldFailWithNeitherDefaultWebHandlerNorWebApplicationContext() {
88+
ClassLoader parentClassLoader = Thread.currentThread().getContextClassLoader();
89+
this.contextRunner.withClassLoader(new FilteredClassLoader(parentClassLoader, WebApplicationContext.class))
90+
.run((context) -> {
91+
assertThat(context).getFailure()
92+
.rootCause()
93+
.isInstanceOf(RuntimeException.class)
94+
.hasMessageStartingWith("Mock WebTestClient support requires")
95+
.hasMessageContaining("WebApplicationContext");
96+
});
97+
}
98+
7499
@Test
75100
@WithResource(name = "META-INF/spring.factories", content = """
76101
org.springframework.boot.test.http.server.BaseUrlProvider=\
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* Copyright 2012-present 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 smoketest.webflux;
18+
19+
import org.junit.jupiter.api.Test;
20+
import reactor.core.publisher.Mono;
21+
22+
import org.springframework.beans.factory.annotation.Autowired;
23+
import org.springframework.boot.test.context.SpringBootTest;
24+
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
25+
import org.springframework.boot.webtestclient.AutoConfigureWebTestClient;
26+
import org.springframework.http.HttpStatus;
27+
import org.springframework.http.MediaType;
28+
import org.springframework.test.web.reactive.server.WebTestClient;
29+
30+
import static org.assertj.core.api.Assertions.assertThat;
31+
32+
/**
33+
* Basic integration tests for WebFlux application.
34+
*
35+
* @author Brian Clozel
36+
*/
37+
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = "server.error.include-message=always")
38+
@AutoConfigureWebTestClient
39+
class SampleWebFluxApplicationIntegrationTests {
40+
41+
@Autowired
42+
private WebTestClient webClient;
43+
44+
@Test
45+
void testWelcome() {
46+
this.webClient.get()
47+
.uri("/")
48+
.accept(MediaType.TEXT_PLAIN)
49+
.exchange()
50+
.expectBody(String.class)
51+
.isEqualTo("Hello World");
52+
}
53+
54+
@Test
55+
void testEcho() {
56+
this.webClient.post()
57+
.uri("/echo")
58+
.contentType(MediaType.TEXT_PLAIN)
59+
.accept(MediaType.TEXT_PLAIN)
60+
.body(Mono.just("Hello WebFlux!"), String.class)
61+
.exchange()
62+
.expectBody(String.class)
63+
.isEqualTo("Hello WebFlux!");
64+
}
65+
66+
@Test
67+
void testActuatorStatus() {
68+
this.webClient.get()
69+
.uri("/actuator/health")
70+
.accept(MediaType.APPLICATION_JSON)
71+
.exchange()
72+
.expectStatus()
73+
.isOk()
74+
.expectBody()
75+
.json("{\"status\":\"UP\"}");
76+
}
77+
78+
@Test
79+
void templated404ErrorPage() {
80+
this.webClient.get()
81+
.uri("/404")
82+
.accept(MediaType.TEXT_HTML)
83+
.exchange()
84+
.expectStatus()
85+
.isNotFound()
86+
.expectBody(String.class)
87+
.value((body) -> assertThat(body).isEqualToNormalizingNewlines("404 page\n"));
88+
}
89+
90+
@Test
91+
void templated4xxErrorPage() {
92+
this.webClient.get()
93+
.uri("/bad-request")
94+
.accept(MediaType.TEXT_HTML)
95+
.exchange()
96+
.expectStatus()
97+
.isBadRequest()
98+
.expectBody(String.class)
99+
.value((body) -> assertThat(body).isEqualToNormalizingNewlines("4xx page\n"));
100+
}
101+
102+
@Test
103+
void htmlErrorPage() {
104+
this.webClient.get()
105+
.uri("/five-hundred")
106+
.accept(MediaType.TEXT_HTML)
107+
.exchange()
108+
.expectStatus()
109+
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR)
110+
.expectBody(String.class)
111+
.value((body) -> assertThat(body).contains("status: 500").contains("message: Expected!"));
112+
}
113+
114+
}

smoke-test/spring-boot-smoke-test-webflux/src/test/java/smoketest/webflux/SampleWebFluxApplicationTests.java

Lines changed: 18 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -16,41 +16,37 @@
1616

1717
package smoketest.webflux;
1818

19+
import java.util.Map;
20+
1921
import org.junit.jupiter.api.Test;
2022
import reactor.core.publisher.Mono;
2123

2224
import org.springframework.beans.factory.annotation.Autowired;
2325
import org.springframework.boot.test.context.SpringBootTest;
24-
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
2526
import org.springframework.boot.webtestclient.AutoConfigureWebTestClient;
27+
import org.springframework.core.ParameterizedTypeReference;
2628
import org.springframework.http.HttpStatus;
2729
import org.springframework.http.MediaType;
2830
import org.springframework.test.web.reactive.server.WebTestClient;
2931

3032
import static org.assertj.core.api.Assertions.assertThat;
3133

3234
/**
33-
* Basic integration tests for WebFlux application.
35+
* Basic tests for a WebFlux application, configuring the {@link WebTestClient} to test
36+
* without a running server.
3437
*
35-
* @author Brian Clozel
38+
* @author Stephane Nicoll
3639
*/
37-
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = "server.error.include-message=always")
40+
@SpringBootTest
3841
@AutoConfigureWebTestClient
3942
class SampleWebFluxApplicationTests {
4043

44+
private static final ParameterizedTypeReference<Map<String, Object>> MAP_TYPE = new ParameterizedTypeReference<Map<String, Object>>() {
45+
};
46+
4147
@Autowired
4248
private WebTestClient webClient;
4349

44-
@Test
45-
void testWelcome() {
46-
this.webClient.get()
47-
.uri("/")
48-
.accept(MediaType.TEXT_PLAIN)
49-
.exchange()
50-
.expectBody(String.class)
51-
.isEqualTo("Hello World");
52-
}
53-
5450
@Test
5551
void testEcho() {
5652
this.webClient.post()
@@ -64,51 +60,27 @@ void testEcho() {
6460
}
6561

6662
@Test
67-
void testActuatorStatus() {
68-
this.webClient.get()
69-
.uri("/actuator/health")
70-
.accept(MediaType.APPLICATION_JSON)
71-
.exchange()
72-
.expectStatus()
73-
.isOk()
74-
.expectBody()
75-
.json("{\"status\":\"UP\"}");
76-
}
77-
78-
@Test
79-
void templated404ErrorPage() {
80-
this.webClient.get()
81-
.uri("/404")
82-
.accept(MediaType.TEXT_HTML)
83-
.exchange()
84-
.expectStatus()
85-
.isNotFound()
86-
.expectBody(String.class)
87-
.value((body) -> assertThat(body).isEqualToNormalizingNewlines("404 page\n"));
88-
}
89-
90-
@Test
91-
void templated4xxErrorPage() {
63+
void testBadRequest() {
9264
this.webClient.get()
9365
.uri("/bad-request")
94-
.accept(MediaType.TEXT_HTML)
66+
.accept(MediaType.APPLICATION_JSON)
9567
.exchange()
9668
.expectStatus()
9769
.isBadRequest()
98-
.expectBody(String.class)
99-
.value((body) -> assertThat(body).isEqualToNormalizingNewlines("4xx page\n"));
70+
.expectBody(MAP_TYPE)
71+
.value((content) -> assertThat(content).containsEntry("path", "/bad-request"));
10072
}
10173

10274
@Test
103-
void htmlErrorPage() {
75+
void testServerError() {
10476
this.webClient.get()
10577
.uri("/five-hundred")
106-
.accept(MediaType.TEXT_HTML)
78+
.accept(MediaType.APPLICATION_JSON)
10779
.exchange()
10880
.expectStatus()
10981
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR)
110-
.expectBody(String.class)
111-
.value((body) -> assertThat(body).contains("status: 500").contains("message: Expected!"));
82+
.expectBody(MAP_TYPE)
83+
.value((content) -> assertThat(content).containsEntry("path", "/five-hundred"));
11284
}
11385

11486
}

0 commit comments

Comments
 (0)