Since Spring 6.2, a new API - MockMvcTester
has been introduced to test WebMvc controllers. MockMvcTester
is based on the existing MockMvc
but is tightly integrated with the AssertJ fluent Assertions APIs, allowing developers to assert the HTTP response with AssertJ-style code.
There are several options for creating a MockMvcTester
instance in your test code:
MockMvcTester.from(...)
is used to create aMockMvcTester
instance from anApplicationContext
.MockMvcTester.create(...)
allows developers to create aMockMvcTester
instance from an existingMockMvc
.MockMvcTester.of(...)
can specify certain controllers you want to test with the createdMockMvcTester
instance.
The following is an example of creating a MockMvcTester
with a Spring ApplicationContext
, and configuring the underlying MockMvc
with a Function<MockMvcBuilder, MockMvc>
parameter.
MockMvcTester.from(ctx, builder ->
builder.addDispatcherServletCustomizer(dispatcherServlet ->
dispatcherServlet.setEnableLoggingRequestDetails(true)
)
.build()
)
The dispatcherServlet.setEnableLoggingRequestDetails(true)
enables logging for request details, which is very useful for debugging issues in the request/response process.
Assume you have created a Spring WebMvc project and exposed the following RESTful APIs through controllers:
GET /api/posts
- Get all posts.GET /api/posts/{id}
- Get post by id, if not found, return a 404 error.
Note
We are focusing on the usage of MockMvcTester
in this post. Explore the PostRestController
source code that produces these APIs yourself if you are interested.
The following test code covers the get all posts case.
@Test
public void getAllPosts() throws Exception {
when(this.posts.findAll())
.thenReturn(List.of(
Post.of("test", "content of test1"),
Post.of("test2", "content of test2")
)
);
assertThat(this.mvc.perform(get("/api/posts").accept(MediaType.APPLICATION_JSON)))
.hasStatusOk()
.bodyJson()
.hasPathSatisfying("$.size()", v -> v.assertThat().isEqualTo(2))
.extractingPath("$[0].title").isEqualTo("test");
verify(this.posts, times(1)).findAll();
verifyNoMoreInteractions(this.posts);
}
The MockMvcTester.perform()
will delegate the request building process to a RequestBuilder
and send it to the mocking Servlet engine, returning an AssertJ-aware MvcTestResult
. MockMvcTester
also provides several convenient methods mapped to popular HTTP methods, including get
, post
, put
, delete
, head
, options
, etc., which return a MockMvcRequestBuilder
. The MockMvcRequestBuilder.exchange()
will return a MvcTestResult
.
With assertThat(MvcTestResult)
or assertThat(MockMvcRequestBuilder)
, we can assert the HTTP response result, including status, headers, body, etc.
In the above code snippet, we checked if the status and body content of the HTTP response were returned as expected. Most of the time, we do not compare the whole response content; using JsonPath to evaluate the value of a JSON node is the simplest way to verify the JSON data.
Let's move to another test case - get post by id.
@Test
public void getPostById() {
when(this.posts.findById(any(UUID.class)))
.thenReturn(
Optional.of(Post.of("test", "content of test1"))
);
assertThat(this.mvc.get().uri("/api/posts/{id}", UUID.randomUUID()).accept(MediaType.APPLICATION_JSON))
.hasStatusOk()
.bodyJson().hasPath("$.title");
verify(this.posts, times(1)).findById(any(UUID.class));
verifyNoMoreInteractions(this.posts);
}
Here we use the MockMvcTester.get()
to build a request and check the existence of a JSON node by bodyJson().hasPath("$.title")
.
Next, let's look at the test code for the post not found case.
@Test
public void getPostByIdNotFoundException() {
when(this.posts.findById(any(UUID.class))).thenReturn(Optional.empty());
assertThat(this.mvc.perform(get("/api/posts/{id}", UUID.randomUUID()).accept(MediaType.APPLICATION_JSON)))
.hasStatus4xxClientError()
// throws PostNotFoundException
.hasFailed().failure()
.isInstanceOf(PostNotFoundException.class)
.hasMessageContaining("not found");
verify(this.posts, times(1)).findById(any(UUID.class));
verifyNoMoreInteractions(this.posts);
}
Here we can check if the exception is the expected one handled by controller advice.
Note
Check the exception handler for PostNotFoundException
here.
For more details on using MockMvcTester
, check the javadoc of MockMvcTester
, MvcTestResult
, and MockMvcRequestBuilder
, MockMultipartMvcRequestBuilder
if you are handling multipart requests in test code.
Check the complete testing example on my GitHub account.