Skip to content

Commit e5859ae

Browse files
committed
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
1 parent 42c29d3 commit e5859ae

File tree

12 files changed

+277
-81
lines changed

12 files changed

+277
-81
lines changed

spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/spring-boot-applications.adoc

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,14 @@ include-code::MyApplicationArgumentTests[]
140140

141141
By default, `@SpringBootTest` does not start the server but instead sets up a mock environment for testing web endpoints.
142142

143-
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:
143+
With Spring MVC, we can query our web endpoints using {url-spring-framework-docs}/testing/mockmvc.html[`MockMvc`].
144+
Three integrations are available:
145+
146+
* The regular {url-spring-framework-docs}/testing/mockmvc/hamcrest.html[`MockMvc`] that uses Hamcrest.
147+
* {url-spring-framework-docs}/testing/mockmvc/assertj.html[`MockMvcTester`] that wraps `MockMvc` and uses AssertJ.
148+
* {url-spring-framework-docs}/testing/webtestclient.html[`WebTestClient`] where `MockMvc` is plugged in as the server to handle requests with.
149+
150+
The following example showcases the available integrations:
144151

145152
include-code::MyMockMvcTests[]
146153

@@ -345,9 +352,10 @@ Often, `@WebMvcTest` is limited to a single controller and is used in combinatio
345352

346353
`@WebMvcTest` also auto-configures `MockMvc`.
347354
Mock MVC offers a powerful way to quickly test MVC controllers without needing to start a full HTTP server.
355+
If AssertJ is available, the AssertJ support provided by `MockMvcTester` is auto-configured as well.
348356

349-
TIP: You can also auto-configure `MockMvc` in a non-`@WebMvcTest` (such as `@SpringBootTest`) by annotating it with `@AutoConfigureMockMvc`.
350-
The following example uses `MockMvc`:
357+
TIP: You can also auto-configure `MockMvc` and `MockMvcTester` in a non-`@WebMvcTest` (such as `@SpringBootTest`) by annotating it with `@AutoConfigureMockMvc`.
358+
The following example uses `MockMvcTester`:
351359

352360
include-code::MyControllerTests[]
353361

@@ -753,7 +761,13 @@ It can also be used to configure the host, scheme, and port that appears in any
753761
`@AutoConfigureRestDocs` customizes the `MockMvc` bean to use Spring REST Docs when testing servlet-based web applications.
754762
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:
755763

756-
include-code::MyUserDocumentationTests[]
764+
include-code::hamcrest/MyUserDocumentationTests[]
765+
766+
If you prefer to use the AssertJ integration, `MockMvcTester` is available as well, as shown in the following example:
767+
768+
include-code::assertj/MyUserDocumentationTests[]
769+
770+
Both reuses the same `MockMvc` instance behind the scenes so any configuration to it applies to both.
757771

758772
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:
759773

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2012-2024 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.docs.testing.springbootapplications.autoconfiguredspringrestdocs.withmockmvc.assertj;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import org.springframework.beans.factory.annotation.Autowired;
22+
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
23+
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
24+
import org.springframework.http.MediaType;
25+
import org.springframework.test.web.servlet.assertj.MockMvcTester;
26+
27+
import static org.assertj.core.api.Assertions.assertThat;
28+
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
29+
30+
@WebMvcTest(UserController.class)
31+
@AutoConfigureRestDocs
32+
class MyUserDocumentationTests {
33+
34+
@Autowired
35+
private MockMvcTester mvc;
36+
37+
@Test
38+
void listUsers() {
39+
assertThat(this.mvc.get().uri("/users").accept(MediaType.TEXT_PLAIN)).hasStatusOk()
40+
.apply(document("list-users"));
41+
}
42+
43+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringrestdocs.withmockmvc;
17+
package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringrestdocs.withmockmvc.assertj;
1818

1919
class UserController {
2020

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringrestdocs.withmockmvc;
17+
package org.springframework.boot.docs.testing.springbootapplications.autoconfiguredspringrestdocs.withmockmvc.hamcrest;
1818

1919
import org.junit.jupiter.api.Test;
2020

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright 2012-2024 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.docs.testing.springbootapplications.autoconfiguredspringrestdocs.withmockmvc.hamcrest;
18+
19+
class UserController {
20+
21+
}

spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/springmvctests/MyControllerTests.java

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,30 +22,28 @@
2222
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
2323
import org.springframework.boot.test.mock.mockito.MockBean;
2424
import org.springframework.http.MediaType;
25-
import org.springframework.test.web.servlet.MockMvc;
25+
import org.springframework.test.web.servlet.assertj.MockMvcTester;
2626

27+
import static org.assertj.core.api.Assertions.assertThat;
2728
import static org.mockito.BDDMockito.given;
28-
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
29-
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
30-
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
3129

3230
@WebMvcTest(UserVehicleController.class)
3331
class MyControllerTests {
3432

3533
@Autowired
36-
private MockMvc mvc;
34+
private MockMvcTester mvc;
3735

3836
@MockBean
3937
private UserVehicleService userVehicleService;
4038

4139
@Test
42-
void testExample() throws Exception {
40+
void testExample() {
4341
// @formatter:off
4442
given(this.userVehicleService.getVehicleDetails("sboot"))
4543
.willReturn(new VehicleDetails("Honda", "Civic"));
46-
this.mvc.perform(get("/sboot/vehicle").accept(MediaType.TEXT_PLAIN))
47-
.andExpect(status().isOk())
48-
.andExpect(content().string("Honda Civic"));
44+
assertThat(this.mvc.get().uri("/sboot/vehicle").accept(MediaType.TEXT_PLAIN))
45+
.hasStatusOk()
46+
.hasBodyTextEqualTo("Honda Civic");
4947
// @formatter:on
5048
}
5149

spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/testing/springbootapplications/withmockenvironment/MyMockMvcTests.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
import org.springframework.boot.test.context.SpringBootTest;
2424
import org.springframework.test.web.reactive.server.WebTestClient;
2525
import org.springframework.test.web.servlet.MockMvc;
26+
import org.springframework.test.web.servlet.assertj.MockMvcTester;
2627

28+
import static org.assertj.core.api.Assertions.assertThat;
2729
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
2830
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
2931
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -37,6 +39,12 @@ void testWithMockMvc(@Autowired MockMvc mvc) throws Exception {
3739
mvc.perform(get("/")).andExpect(status().isOk()).andExpect(content().string("Hello World"));
3840
}
3941

42+
// If AssertJ is on the classpath, you can use MockMvcTester
43+
@Test
44+
void testWithMockMvcTester(@Autowired MockMvcTester mvc) {
45+
assertThat(mvc.get().uri("/")).hasStatusOk().hasBodyTextEqualTo("Hello World");
46+
}
47+
4048
// If Spring WebFlux is on the classpath, you can drive MVC tests with a WebTestClient
4149
@Test
4250
void testWithWebTestClient(@Autowired WebTestClient webClient) {

spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/AutoConfigureMockMvc.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,12 @@
3131
import org.springframework.boot.test.autoconfigure.properties.SkipPropertyMapping;
3232
import org.springframework.test.web.servlet.MockMvc;
3333
import org.springframework.test.web.servlet.MvcResult;
34+
import org.springframework.test.web.servlet.assertj.MockMvcTester;
3435

3536
/**
3637
* Annotation that can be applied to a test class to enable and configure
37-
* auto-configuration of {@link MockMvc}.
38+
* auto-configuration of {@link MockMvc}. If AssertJ is available a {@link MockMvcTester}
39+
* is auto-configured as well.
3840
*
3941
* @author Phillip Webb
4042
* @since 1.4.0
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 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.
@@ -27,20 +27,15 @@
2727
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath;
2828
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
2929
import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties;
30-
import org.springframework.boot.context.properties.ConfigurationProperties;
3130
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3231
import org.springframework.boot.test.autoconfigure.web.reactive.WebTestClientAutoConfiguration;
3332
import org.springframework.boot.test.web.reactive.server.WebTestClientBuilderCustomizer;
3433
import org.springframework.context.annotation.Bean;
3534
import org.springframework.context.annotation.Configuration;
35+
import org.springframework.context.annotation.Import;
3636
import org.springframework.test.web.reactive.server.WebTestClient;
37-
import org.springframework.test.web.servlet.DispatcherServletCustomizer;
3837
import org.springframework.test.web.servlet.MockMvc;
39-
import org.springframework.test.web.servlet.MockMvcBuilder;
4038
import org.springframework.test.web.servlet.client.MockMvcWebTestClient;
41-
import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder;
42-
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
43-
import org.springframework.web.context.WebApplicationContext;
4439
import org.springframework.web.reactive.function.client.WebClient;
4540
import org.springframework.web.servlet.DispatcherServlet;
4641

@@ -57,44 +52,13 @@
5752
@AutoConfiguration(after = { WebMvcAutoConfiguration.class, WebTestClientAutoConfiguration.class })
5853
@ConditionalOnWebApplication(type = Type.SERVLET)
5954
@EnableConfigurationProperties({ ServerProperties.class, WebMvcProperties.class })
55+
@Import({ MockMvcConfiguration.class, MockMvcTesterConfiguration.class })
6056
public class MockMvcAutoConfiguration {
6157

62-
private final WebApplicationContext context;
63-
64-
private final WebMvcProperties webMvcProperties;
65-
66-
MockMvcAutoConfiguration(WebApplicationContext context, WebMvcProperties webMvcProperties) {
67-
this.context = context;
68-
this.webMvcProperties = webMvcProperties;
69-
}
70-
7158
@Bean
7259
@ConditionalOnMissingBean
73-
public DispatcherServletPath dispatcherServletPath() {
74-
return () -> this.webMvcProperties.getServlet().getPath();
75-
}
76-
77-
@Bean
78-
@ConditionalOnMissingBean(MockMvcBuilder.class)
79-
public DefaultMockMvcBuilder mockMvcBuilder(List<MockMvcBuilderCustomizer> customizers) {
80-
DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(this.context);
81-
builder.addDispatcherServletCustomizer(new MockMvcDispatcherServletCustomizer(this.webMvcProperties));
82-
for (MockMvcBuilderCustomizer customizer : customizers) {
83-
customizer.customize(builder);
84-
}
85-
return builder;
86-
}
87-
88-
@Bean
89-
@ConfigurationProperties(prefix = "spring.test.mockmvc")
90-
public SpringBootMockMvcBuilderCustomizer springBootMockMvcBuilderCustomizer() {
91-
return new SpringBootMockMvcBuilderCustomizer(this.context);
92-
}
93-
94-
@Bean
95-
@ConditionalOnMissingBean
96-
public MockMvc mockMvc(MockMvcBuilder builder) {
97-
return builder.build();
60+
public DispatcherServletPath dispatcherServletPath(WebMvcProperties webMvcProperties) {
61+
return () -> webMvcProperties.getServlet().getPath();
9862
}
9963

10064
@Bean
@@ -119,27 +83,4 @@ WebTestClient webTestClient(MockMvc mockMvc, List<WebTestClientBuilderCustomizer
11983

12084
}
12185

122-
private static class MockMvcDispatcherServletCustomizer implements DispatcherServletCustomizer {
123-
124-
private final WebMvcProperties webMvcProperties;
125-
126-
MockMvcDispatcherServletCustomizer(WebMvcProperties webMvcProperties) {
127-
this.webMvcProperties = webMvcProperties;
128-
}
129-
130-
@Override
131-
public void customize(DispatcherServlet dispatcherServlet) {
132-
dispatcherServlet.setDispatchOptionsRequest(this.webMvcProperties.isDispatchOptionsRequest());
133-
dispatcherServlet.setDispatchTraceRequest(this.webMvcProperties.isDispatchTraceRequest());
134-
configureThrowExceptionIfNoHandlerFound(dispatcherServlet);
135-
}
136-
137-
@SuppressWarnings({ "deprecation", "removal" })
138-
private void configureThrowExceptionIfNoHandlerFound(DispatcherServlet dispatcherServlet) {
139-
dispatcherServlet
140-
.setThrowExceptionIfNoHandlerFound(this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
141-
}
142-
143-
}
144-
14586
}

0 commit comments

Comments
 (0)