From e5859aedafc6f9168dbf87a7b16324481da0d5ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Mon, 29 Jan 2024 16:58:35 +0100 Subject: [PATCH] Add auto-configuration for MockMvcTester This commit adds auto-configuration and documentation for MockMvcTester, a wrapper of MockMvc that provides AssertJ integration as well as a fluent API to build requests. The main differences compared to the regular MockMvc are as follows: * No need for static imports for building requests and define assertions * No need to handle unchecked exception as they can be asserted instead * Support for converting the response body to data types Closes gh-41198 --- .../testing/spring-boot-applications.adoc | 22 ++++- .../assertj/MyUserDocumentationTests.java | 43 ++++++++ .../{ => assertj}/UserController.java | 2 +- .../MyUserDocumentationTests.java | 2 +- .../withmockmvc/hamcrest/UserController.java | 21 ++++ .../springmvctests/MyControllerTests.java | 16 ++- .../withmockenvironment/MyMockMvcTests.java | 8 ++ .../web/servlet/AutoConfigureMockMvc.java | 4 +- .../web/servlet/MockMvcAutoConfiguration.java | 69 +------------ .../web/servlet/MockMvcConfiguration.java | 97 +++++++++++++++++++ .../servlet/MockMvcTesterConfiguration.java | 48 +++++++++ .../MockMvcAutoConfigurationTests.java | 26 ++++- 12 files changed, 277 insertions(+), 81 deletions(-) create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/assertj/MyUserDocumentationTests.java rename spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/{ => assertj}/UserController.java (92%) rename spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/{ => hamcrest}/MyUserDocumentationTests.java (96%) create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/hamcrest/UserController.java create mode 100644 spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcConfiguration.java create mode 100644 spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcTesterConfiguration.java diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/spring-boot-applications.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/spring-boot-applications.adoc index 3e9048d48d..923b4d22ed 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/spring-boot-applications.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/spring-boot-applications.adoc @@ -140,7 +140,14 @@ include-code::MyApplicationArgumentTests[] By default, `@SpringBootTest` does not start the server but instead sets up a mock environment for testing web endpoints. -With Spring MVC, we can query our web endpoints using {url-spring-framework-docs}/testing/spring-mvc-test-framework.html[`MockMvc`] or `WebTestClient`, as shown in the following example: +With Spring MVC, we can query our web endpoints using {url-spring-framework-docs}/testing/mockmvc.html[`MockMvc`]. +Three integrations are available: + +* The regular {url-spring-framework-docs}/testing/mockmvc/hamcrest.html[`MockMvc`] that uses Hamcrest. +* {url-spring-framework-docs}/testing/mockmvc/assertj.html[`MockMvcTester`] that wraps `MockMvc` and uses AssertJ. +* {url-spring-framework-docs}/testing/webtestclient.html[`WebTestClient`] where `MockMvc` is plugged in as the server to handle requests with. + +The following example showcases the available integrations: include-code::MyMockMvcTests[] @@ -345,9 +352,10 @@ Often, `@WebMvcTest` is limited to a single controller and is used in combinatio `@WebMvcTest` also auto-configures `MockMvc`. Mock MVC offers a powerful way to quickly test MVC controllers without needing to start a full HTTP server. +If AssertJ is available, the AssertJ support provided by `MockMvcTester` is auto-configured as well. -TIP: You can also auto-configure `MockMvc` in a non-`@WebMvcTest` (such as `@SpringBootTest`) by annotating it with `@AutoConfigureMockMvc`. -The following example uses `MockMvc`: +TIP: You can also auto-configure `MockMvc` and `MockMvcTester` in a non-`@WebMvcTest` (such as `@SpringBootTest`) by annotating it with `@AutoConfigureMockMvc`. +The following example uses `MockMvcTester`: include-code::MyControllerTests[] @@ -753,7 +761,13 @@ It can also be used to configure the host, scheme, and port that appears in any `@AutoConfigureRestDocs` customizes the `MockMvc` bean to use Spring REST Docs when testing servlet-based web applications. You can inject it by using `@Autowired` and use it in your tests as you normally would when using Mock MVC and Spring REST Docs, as shown in the following example: -include-code::MyUserDocumentationTests[] +include-code::hamcrest/MyUserDocumentationTests[] + +If you prefer to use the AssertJ integration, `MockMvcTester` is available as well, as shown in the following example: + +include-code::assertj/MyUserDocumentationTests[] + +Both reuses the same `MockMvc` instance behind the scenes so any configuration to it applies to both. If you require more control over Spring REST Docs configuration than offered by the attributes of `@AutoConfigureRestDocs`, you can use a `RestDocsMockMvcConfigurationCustomizer` bean, as shown in the following example: diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/assertj/MyUserDocumentationTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/assertj/MyUserDocumentationTests.java new file mode 100644 index 0000000000..ee492fcb17 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/assertj/MyUserDocumentationTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2024 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.docs.testing.springbootapplications.autoconfiguredspringrestdocs.withmockmvc.assertj; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.assertj.MockMvcTester; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; + +@WebMvcTest(UserController.class) +@AutoConfigureRestDocs +class MyUserDocumentationTests { + + @Autowired + private MockMvcTester mvc; + + @Test + void listUsers() { + assertThat(this.mvc.get().uri("/users").accept(MediaType.TEXT_PLAIN)).hasStatusOk() + .apply(document("list-users")); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/UserController.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/assertj/UserController.java similarity index 92% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/UserController.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/assertj/UserController.java index 9c94a3ecd3..c385c282ce 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/UserController.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/assertj/UserController.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringrestdocs.withmockmvc; +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringrestdocs.withmockmvc.assertj; class UserController { diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyUserDocumentationTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/hamcrest/MyUserDocumentationTests.java similarity index 96% rename from spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyUserDocumentationTests.java rename to spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/hamcrest/MyUserDocumentationTests.java index a84d10deca..2c17338ff0 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/MyUserDocumentationTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/hamcrest/MyUserDocumentationTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringrestdocs.withmockmvc; +package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringrestdocs.withmockmvc.hamcrest; import org.junit.jupiter.api.Test; diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/hamcrest/UserController.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/hamcrest/UserController.java new file mode 100644 index 0000000000..c08fd60763 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/autoconfiguredspringrestdocs/withmockmvc/hamcrest/UserController.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012-2024 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.docs.testing.springbootapplications.autoconfiguredspringrestdocs.withmockmvc.hamcrest; + +class UserController { + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springmvctests/MyControllerTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springmvctests/MyControllerTests.java index 7c0e2a06cd..df1eafb0fd 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springmvctests/MyControllerTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springmvctests/MyControllerTests.java @@ -22,30 +22,28 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.assertj.MockMvcTester; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @WebMvcTest(UserVehicleController.class) class MyControllerTests { @Autowired - private MockMvc mvc; + private MockMvcTester mvc; @MockBean private UserVehicleService userVehicleService; @Test - void testExample() throws Exception { + void testExample() { // @formatter:off given(this.userVehicleService.getVehicleDetails("sboot")) .willReturn(new VehicleDetails("Honda", "Civic")); - this.mvc.perform(get("/sboot/vehicle").accept(MediaType.TEXT_PLAIN)) - .andExpect(status().isOk()) - .andExpect(content().string("Honda Civic")); + assertThat(this.mvc.get().uri("/sboot/vehicle").accept(MediaType.TEXT_PLAIN)) + .hasStatusOk() + .hasBodyTextEqualTo("Honda Civic"); // @formatter:on } diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/withmockenvironment/MyMockMvcTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/withmockenvironment/MyMockMvcTests.java index 8fa8c548a1..15c68728dc 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/withmockenvironment/MyMockMvcTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/withmockenvironment/MyMockMvcTests.java @@ -23,7 +23,9 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.assertj.MockMvcTester; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -37,6 +39,12 @@ class MyMockMvcTests { mvc.perform(get("/")).andExpect(status().isOk()).andExpect(content().string("Hello World")); } + // If AssertJ is on the classpath, you can use MockMvcTester + @Test + void testWithMockMvcTester(@Autowired MockMvcTester mvc) { + assertThat(mvc.get().uri("/")).hasStatusOk().hasBodyTextEqualTo("Hello World"); + } + // If Spring WebFlux is on the classpath, you can drive MVC tests with a WebTestClient @Test void testWithWebTestClient(@Autowired WebTestClient webClient) { diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/AutoConfigureMockMvc.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/AutoConfigureMockMvc.java index 2ed2f06d81..a0aefc9c14 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/AutoConfigureMockMvc.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/AutoConfigureMockMvc.java @@ -31,10 +31,12 @@ import org.springframework.boot.test.autoconfigure.properties.PropertyMapping; import org.springframework.boot.test.autoconfigure.properties.SkipPropertyMapping; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.assertj.MockMvcTester; /** * Annotation that can be applied to a test class to enable and configure - * auto-configuration of {@link MockMvc}. + * auto-configuration of {@link MockMvc}. If AssertJ is available a {@link MockMvcTester} + * is auto-configured as well. * * @author Phillip Webb * @since 1.4.0 diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfiguration.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfiguration.java index 9e944e4fad..6b96b5bd22 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfiguration.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -27,20 +27,15 @@ import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties; -import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.autoconfigure.web.reactive.WebTestClientAutoConfiguration; import org.springframework.boot.test.web.reactive.server.WebTestClientBuilderCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.test.web.servlet.DispatcherServletCustomizer; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MockMvcBuilder; import org.springframework.test.web.servlet.client.MockMvcWebTestClient; -import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.servlet.DispatcherServlet; @@ -57,44 +52,13 @@ import org.springframework.web.servlet.DispatcherServlet; @AutoConfiguration(after = { WebMvcAutoConfiguration.class, WebTestClientAutoConfiguration.class }) @ConditionalOnWebApplication(type = Type.SERVLET) @EnableConfigurationProperties({ ServerProperties.class, WebMvcProperties.class }) +@Import({ MockMvcConfiguration.class, MockMvcTesterConfiguration.class }) public class MockMvcAutoConfiguration { - private final WebApplicationContext context; - - private final WebMvcProperties webMvcProperties; - - MockMvcAutoConfiguration(WebApplicationContext context, WebMvcProperties webMvcProperties) { - this.context = context; - this.webMvcProperties = webMvcProperties; - } - @Bean @ConditionalOnMissingBean - public DispatcherServletPath dispatcherServletPath() { - return () -> this.webMvcProperties.getServlet().getPath(); - } - - @Bean - @ConditionalOnMissingBean(MockMvcBuilder.class) - public DefaultMockMvcBuilder mockMvcBuilder(List customizers) { - DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(this.context); - builder.addDispatcherServletCustomizer(new MockMvcDispatcherServletCustomizer(this.webMvcProperties)); - for (MockMvcBuilderCustomizer customizer : customizers) { - customizer.customize(builder); - } - return builder; - } - - @Bean - @ConfigurationProperties(prefix = "spring.test.mockmvc") - public SpringBootMockMvcBuilderCustomizer springBootMockMvcBuilderCustomizer() { - return new SpringBootMockMvcBuilderCustomizer(this.context); - } - - @Bean - @ConditionalOnMissingBean - public MockMvc mockMvc(MockMvcBuilder builder) { - return builder.build(); + public DispatcherServletPath dispatcherServletPath(WebMvcProperties webMvcProperties) { + return () -> webMvcProperties.getServlet().getPath(); } @Bean @@ -119,27 +83,4 @@ public class MockMvcAutoConfiguration { } - private static class MockMvcDispatcherServletCustomizer implements DispatcherServletCustomizer { - - private final WebMvcProperties webMvcProperties; - - MockMvcDispatcherServletCustomizer(WebMvcProperties webMvcProperties) { - this.webMvcProperties = webMvcProperties; - } - - @Override - public void customize(DispatcherServlet dispatcherServlet) { - dispatcherServlet.setDispatchOptionsRequest(this.webMvcProperties.isDispatchOptionsRequest()); - dispatcherServlet.setDispatchTraceRequest(this.webMvcProperties.isDispatchTraceRequest()); - configureThrowExceptionIfNoHandlerFound(dispatcherServlet); - } - - @SuppressWarnings({ "deprecation", "removal" }) - private void configureThrowExceptionIfNoHandlerFound(DispatcherServlet dispatcherServlet) { - dispatcherServlet - .setThrowExceptionIfNoHandlerFound(this.webMvcProperties.isThrowExceptionIfNoHandlerFound()); - } - - } - } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcConfiguration.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcConfiguration.java new file mode 100644 index 0000000000..6ed6eb9702 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcConfiguration.java @@ -0,0 +1,97 @@ +/* + * Copyright 2012-2024 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.autoconfigure.web.servlet; + +import java.util.List; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.web.servlet.DispatcherServletCustomizer; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MockMvcBuilder; +import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.servlet.DispatcherServlet; + +/** + * Configuration for core {@link MockMvc}. + * + * @author Stephane Nicoll + */ +@Configuration(proxyBeanMethods = false) +class MockMvcConfiguration { + + private final WebApplicationContext context; + + private final WebMvcProperties webMvcProperties; + + MockMvcConfiguration(WebApplicationContext context, WebMvcProperties webMvcProperties) { + this.context = context; + this.webMvcProperties = webMvcProperties; + } + + @Bean + @ConditionalOnMissingBean(MockMvcBuilder.class) + DefaultMockMvcBuilder mockMvcBuilder(List customizers) { + DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(this.context); + builder.addDispatcherServletCustomizer(new MockMvcDispatcherServletCustomizer(this.webMvcProperties)); + for (MockMvcBuilderCustomizer customizer : customizers) { + customizer.customize(builder); + } + return builder; + } + + @Bean + @ConfigurationProperties(prefix = "spring.test.mockmvc") + SpringBootMockMvcBuilderCustomizer springBootMockMvcBuilderCustomizer() { + return new SpringBootMockMvcBuilderCustomizer(this.context); + } + + @Bean + @ConditionalOnMissingBean + MockMvc mockMvc(MockMvcBuilder builder) { + return builder.build(); + } + + private static class MockMvcDispatcherServletCustomizer implements DispatcherServletCustomizer { + + private final WebMvcProperties webMvcProperties; + + MockMvcDispatcherServletCustomizer(WebMvcProperties webMvcProperties) { + this.webMvcProperties = webMvcProperties; + } + + @Override + public void customize(DispatcherServlet dispatcherServlet) { + dispatcherServlet.setDispatchOptionsRequest(this.webMvcProperties.isDispatchOptionsRequest()); + dispatcherServlet.setDispatchTraceRequest(this.webMvcProperties.isDispatchTraceRequest()); + configureThrowExceptionIfNoHandlerFound(dispatcherServlet); + } + + @SuppressWarnings({ "deprecation", "removal" }) + private void configureThrowExceptionIfNoHandlerFound(DispatcherServlet dispatcherServlet) { + dispatcherServlet + .setThrowExceptionIfNoHandlerFound(this.webMvcProperties.isThrowExceptionIfNoHandlerFound()); + } + + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcTesterConfiguration.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcTesterConfiguration.java new file mode 100644 index 0000000000..657dbb2e40 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcTesterConfiguration.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-2024 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.autoconfigure.web.servlet; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.assertj.MockMvcTester; + +/** + * Configuration for {@link MockMvcTester}. + * + * @author Stephane Nicoll + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(name = "org.assertj.core.api.Assert") +class MockMvcTesterConfiguration { + + @Bean + @ConditionalOnMissingBean + MockMvcTester mockMvcTester(MockMvc mockMvc, ObjectProvider httpMessageConverters) { + MockMvcTester mockMvcTester = MockMvcTester.create(mockMvc); + HttpMessageConverters converters = httpMessageConverters.getIfAvailable(); + if (converters != null) { + mockMvcTester = mockMvcTester.withHttpMessageConverters(converters); + } + return mockMvcTester; + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfigurationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfigurationTests.java index 4c88271d30..7151ef6679 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -26,6 +26,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.RequestBuilder; +import org.springframework.test.web.servlet.assertj.MockMvcTester; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.servlet.DispatcherServlet; @@ -54,6 +56,28 @@ class MockMvcAutoConfigurationTests { }); } + @Test + void registersMockMvcTester() { + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(MockMvcTester.class)); + } + + @Test + void shouldNotRegisterMockMvcTesterIfAssertJMissing() { + this.contextRunner.withClassLoader(new FilteredClassLoader(org.assertj.core.api.Assert.class)) + .run((context) -> assertThat(context).doesNotHaveBean(MockMvcTester.class)); + } + + @Test + void registeredMockMvcTesterDelegatesToConfiguredMockMvc() { + MockMvc mockMvc = mock(MockMvc.class); + this.contextRunner.withBean("customMockMvc", MockMvc.class, () -> mockMvc).run((context) -> { + assertThat(context).hasSingleBean(MockMvc.class).hasSingleBean(MockMvcTester.class); + MockMvcTester mvc = context.getBean(MockMvcTester.class); + mvc.get().uri("/dummy").exchange(); + then(mockMvc).should().perform(any(RequestBuilder.class)); + }); + } + @Test void registersWebTestClient() { this.contextRunner.run((context) -> assertThat(context).hasSingleBean(WebTestClient.class));