diff --git a/module/spring-boot-webtestclient/src/main/java/org/springframework/boot/webtestclient/WebTestClientAutoConfiguration.java b/module/spring-boot-webtestclient/src/main/java/org/springframework/boot/webtestclient/WebTestClientAutoConfiguration.java index d13f543724a..2da2c3bc205 100644 --- a/module/spring-boot-webtestclient/src/main/java/org/springframework/boot/webtestclient/WebTestClientAutoConfiguration.java +++ b/module/spring-boot-webtestclient/src/main/java/org/springframework/boot/webtestclient/WebTestClientAutoConfiguration.java @@ -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"); } } diff --git a/module/spring-boot-webtestclient/src/test/java/org/springframework/boot/webflux/test/autoconfigure/WebTestClientAutoConfigurationTests.java b/module/spring-boot-webtestclient/src/test/java/org/springframework/boot/webflux/test/autoconfigure/WebTestClientAutoConfigurationTests.java index e2ef494a75e..9e11e602835 100644 --- a/module/spring-boot-webtestclient/src/test/java/org/springframework/boot/webflux/test/autoconfigure/WebTestClientAutoConfigurationTests.java +++ b/module/spring-boot-webtestclient/src/test/java/org/springframework/boot/webflux/test/autoconfigure/WebTestClientAutoConfigurationTests.java @@ -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=\ diff --git a/smoke-test/spring-boot-smoke-test-webflux/src/test/java/smoketest/webflux/SampleWebFluxApplicationIntegrationTests.java b/smoke-test/spring-boot-smoke-test-webflux/src/test/java/smoketest/webflux/SampleWebFluxApplicationIntegrationTests.java new file mode 100644 index 00000000000..30015b5d2c7 --- /dev/null +++ b/smoke-test/spring-boot-smoke-test-webflux/src/test/java/smoketest/webflux/SampleWebFluxApplicationIntegrationTests.java @@ -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!")); + } + +} diff --git a/smoke-test/spring-boot-smoke-test-webflux/src/test/java/smoketest/webflux/SampleWebFluxApplicationTests.java b/smoke-test/spring-boot-smoke-test-webflux/src/test/java/smoketest/webflux/SampleWebFluxApplicationTests.java index 61315cecc90..17d1cf8c08c 100644 --- a/smoke-test/spring-boot-smoke-test-webflux/src/test/java/smoketest/webflux/SampleWebFluxApplicationTests.java +++ b/smoke-test/spring-boot-smoke-test-webflux/src/test/java/smoketest/webflux/SampleWebFluxApplicationTests.java @@ -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_TYPE = new ParameterizedTypeReference>() { + }; + @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")); } }