mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-09-17 12:09:16 +00:00
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
This commit is contained in:
+6
-16
@@ -18,7 +18,6 @@ package org.springframework.boot.webtestclient;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
@@ -40,7 +39,7 @@ import org.springframework.test.web.servlet.client.MockMvcWebTestClient;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.server.WebHandler;
|
||||
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
|
||||
|
||||
/**
|
||||
* Auto-configuration for {@link WebTestClient}.
|
||||
@@ -79,31 +78,22 @@ public final class WebTestClientAutoConfiguration {
|
||||
if (baseUrl != null) {
|
||||
return WebTestClient.bindToServer().uriBuilderFactory(BaseUrlUriBuilderFactory.get(baseUrl));
|
||||
}
|
||||
if (hasBean(applicationContext, WebHandler.class)) {
|
||||
if (applicationContext.containsBean(WebHttpHandlerBuilder.WEB_HANDLER_BEAN_NAME)) {
|
||||
MockServerSpec<?> spec = WebTestClient.bindToApplicationContext(applicationContext);
|
||||
configurers.forEach(spec::apply);
|
||||
return spec.configureClient();
|
||||
}
|
||||
if (ClassUtils.isPresent(WEB_APPLICATION_CONTEXT_CLASS, applicationContext.getClassLoader())) {
|
||||
if (hasBean(applicationContext, MockMvc.class)) {
|
||||
return MockMvcWebTestClient.bindTo(applicationContext.getBean(MockMvc.class));
|
||||
MockMvc mockMvc = applicationContext.getBeanProvider(MockMvc.class).getIfUnique();
|
||||
if (mockMvc != null) {
|
||||
return MockMvcWebTestClient.bindTo(mockMvc);
|
||||
}
|
||||
if (applicationContext instanceof WebApplicationContext webApplicationContext) {
|
||||
return MockMvcWebTestClient.bindToApplicationContext(webApplicationContext).configureClient();
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
"Mock WebTestClient support requires a WebHandler or MockMvc bean and neither was present");
|
||||
}
|
||||
|
||||
private boolean hasBean(ApplicationContext applicationContext, Class<?> type) {
|
||||
try {
|
||||
applicationContext.getBean(type);
|
||||
return true;
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
return false;
|
||||
}
|
||||
"Mock WebTestClient support requires a WebHandler (named 'webHandler') bean or a WebApplicationContext and neither was present");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+25
@@ -71,6 +71,31 @@ class WebTestClientAutoConfigurationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailWhenDefaultWebHandlerIsNotAvailable() {
|
||||
this.contextRunner.withBean("myWebHandler", WebHandler.class, () -> mock(WebHandler.class)).run((context) -> {
|
||||
assertThat(context).hasFailed();
|
||||
assertThat(context).getFailure()
|
||||
.rootCause()
|
||||
.isInstanceOf(RuntimeException.class)
|
||||
.hasMessageStartingWith("Mock WebTestClient support requires")
|
||||
.hasMessageContaining("WebHandler");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailWithNeitherDefaultWebHandlerNorWebApplicationContext() {
|
||||
ClassLoader parentClassLoader = Thread.currentThread().getContextClassLoader();
|
||||
this.contextRunner.withClassLoader(new FilteredClassLoader(parentClassLoader, WebApplicationContext.class))
|
||||
.run((context) -> {
|
||||
assertThat(context).getFailure()
|
||||
.rootCause()
|
||||
.isInstanceOf(RuntimeException.class)
|
||||
.hasMessageStartingWith("Mock WebTestClient support requires")
|
||||
.hasMessageContaining("WebApplicationContext");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithResource(name = "META-INF/spring.factories", content = """
|
||||
org.springframework.boot.test.http.server.BaseUrlProvider=\
|
||||
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright 2012-present 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 smoketest.webflux;
|
||||
|
||||
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.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.boot.webtestclient.AutoConfigureWebTestClient;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Basic integration tests for WebFlux application.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = "server.error.include-message=always")
|
||||
@AutoConfigureWebTestClient
|
||||
class SampleWebFluxApplicationIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@Test
|
||||
void testWelcome() {
|
||||
this.webClient.get()
|
||||
.uri("/")
|
||||
.accept(MediaType.TEXT_PLAIN)
|
||||
.exchange()
|
||||
.expectBody(String.class)
|
||||
.isEqualTo("Hello World");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEcho() {
|
||||
this.webClient.post()
|
||||
.uri("/echo")
|
||||
.contentType(MediaType.TEXT_PLAIN)
|
||||
.accept(MediaType.TEXT_PLAIN)
|
||||
.body(Mono.just("Hello WebFlux!"), String.class)
|
||||
.exchange()
|
||||
.expectBody(String.class)
|
||||
.isEqualTo("Hello WebFlux!");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testActuatorStatus() {
|
||||
this.webClient.get()
|
||||
.uri("/actuator/health")
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody()
|
||||
.json("{\"status\":\"UP\"}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void templated404ErrorPage() {
|
||||
this.webClient.get()
|
||||
.uri("/404")
|
||||
.accept(MediaType.TEXT_HTML)
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isNotFound()
|
||||
.expectBody(String.class)
|
||||
.value((body) -> assertThat(body).isEqualToNormalizingNewlines("404 page\n"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void templated4xxErrorPage() {
|
||||
this.webClient.get()
|
||||
.uri("/bad-request")
|
||||
.accept(MediaType.TEXT_HTML)
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isBadRequest()
|
||||
.expectBody(String.class)
|
||||
.value((body) -> assertThat(body).isEqualToNormalizingNewlines("4xx page\n"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void htmlErrorPage() {
|
||||
this.webClient.get()
|
||||
.uri("/five-hundred")
|
||||
.accept(MediaType.TEXT_HTML)
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.expectBody(String.class)
|
||||
.value((body) -> assertThat(body).contains("status: 500").contains("message: Expected!"));
|
||||
}
|
||||
|
||||
}
|
||||
+18
-46
@@ -16,13 +16,15 @@
|
||||
|
||||
package smoketest.webflux;
|
||||
|
||||
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.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.boot.webtestclient.AutoConfigureWebTestClient;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
@@ -30,27 +32,21 @@ import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Basic integration tests for WebFlux application.
|
||||
* Basic tests for a WebFlux application, configuring the {@link WebTestClient} to test
|
||||
* without a running server.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = "server.error.include-message=always")
|
||||
@SpringBootTest
|
||||
@AutoConfigureWebTestClient
|
||||
class SampleWebFluxApplicationTests {
|
||||
|
||||
private static final ParameterizedTypeReference<Map<String, Object>> MAP_TYPE = new ParameterizedTypeReference<Map<String, Object>>() {
|
||||
};
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@Test
|
||||
void testWelcome() {
|
||||
this.webClient.get()
|
||||
.uri("/")
|
||||
.accept(MediaType.TEXT_PLAIN)
|
||||
.exchange()
|
||||
.expectBody(String.class)
|
||||
.isEqualTo("Hello World");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEcho() {
|
||||
this.webClient.post()
|
||||
@@ -64,51 +60,27 @@ class SampleWebFluxApplicationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testActuatorStatus() {
|
||||
void testBadRequest() {
|
||||
this.webClient.get()
|
||||
.uri("/actuator/health")
|
||||
.uri("/bad-request")
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody()
|
||||
.json("{\"status\":\"UP\"}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void templated404ErrorPage() {
|
||||
this.webClient.get()
|
||||
.uri("/404")
|
||||
.accept(MediaType.TEXT_HTML)
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isNotFound()
|
||||
.expectBody(String.class)
|
||||
.value((body) -> assertThat(body).isEqualToNormalizingNewlines("404 page\n"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void templated4xxErrorPage() {
|
||||
this.webClient.get()
|
||||
.uri("/bad-request")
|
||||
.accept(MediaType.TEXT_HTML)
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isBadRequest()
|
||||
.expectBody(String.class)
|
||||
.value((body) -> assertThat(body).isEqualToNormalizingNewlines("4xx page\n"));
|
||||
.expectBody(MAP_TYPE)
|
||||
.value((content) -> assertThat(content).containsEntry("path", "/bad-request"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void htmlErrorPage() {
|
||||
void testServerError() {
|
||||
this.webClient.get()
|
||||
.uri("/five-hundred")
|
||||
.accept(MediaType.TEXT_HTML)
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.expectBody(String.class)
|
||||
.value((body) -> assertThat(body).contains("status: 500").contains("message: Expected!"));
|
||||
.expectBody(MAP_TYPE)
|
||||
.value((content) -> assertThat(content).containsEntry("path", "/five-hundred"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user