Spring Framework 6.2 introduced the new MockMvcTester API which allows us to write cleaner tests for Spring MVC controllers, and when we couple it with Spring Boot's auto-configuration support which was introduced in Spring Boot 3.4.0, configuring MockMvcTester couldn't be easier. This blog post provides a quick introduction to MockMvcTester and highlights its key features.
After we have finished this blog post, we:
- Know what
MockMvcTesteris and understand why we should use it. - Can configure
MockMvcTester. - Know how we can send HTTP requests to the system under test.
- Understand how we can write assertions for the returned HTTP response.
Let's begin.
- You are familiar with
MockMvc - You can configure
MockMvcwhen you are writing unit tests for "normal" controller methods and a REST API
What and Why
MockMvcTester integrates MockMvc with AssertJ. It allows us to send HTTP requests to a Spring MVC controller method and write assertions for the returned response with AssertJ. MockMvcTester has these benefits over the pure MockMvc API:
1. Because MockMvcTester handles resolved exceptions, our tests don't have throw or catch Exception.
2. We don't have to use static imports because we can send HTTP requests to the system under test and specify our expectations (aka write assertions) by using a fluent API.
3. We can write tests for both asynchronous and synchronous request handling logic by using the same API.
4. It's a bit little easier to write comprehensive assertions for JSON documents. For example, if we have to ensure that the returned JSON document contains only the expected attributes (id and name) and we must use the "old" MockMvc API, we have two options:
First, we can write assertions which leverage JsonPath expressions. After we have written the required assertions, our assertion code looks as follows:
mockMvc.perform(
//Build the request
)
.andExpect(jsonPath("$").value(allOf(
hasKey("id"),
hasKey("name")
)))
.andExpect(jsonPath("$.*", hasSize(2)));
This doesn't look too bad, but the problem is that we have to write two test methods which ensure that the system under test:
- Returns the correct attribute values.
- Returns a JSON document that contains only the expected attributes.
Second, we can get the response body as a string and write the required assertion with JSONassert. After we have written the required assertion, our assertion code looks as follows:
String expectedJson = """
{
"id": 1,
"name": "Test"
}
""";
var actualResponseBody = mockMvc.perform(
//Build the request
)
.andReturn()
.getResponse()
.getContentAsString();
JSONAssert.assertEquals(expectedJson, actualResponseBody, true);
If we use JSONAssert, we have to write only one assertion which is a huge plus. However, this approach has three cons:
- We must invoke three methods before we can get the asserted object (actual response body).
- We must remember to pass the expected and actual response bodies in the correct order when we invoke the
static assertEquals()method of theJSONAssertclass. - We must remember to pass
trueas the last argument of theassertEquals()method if we want that the assertion uses strict mode andfalseif we want that the assertion uses lenient mode.
MockMvcTester tutorial prove that MockMvcTester has an elegant API which ensures that writing assertions for JSON documents is a little bit easier.Next, we will find out how we can configure MockMvcTester.
Configuring the MockMvcTester
When we configure MockMvcTester, we should use one of these three options:
1. If we are writing unit tests for a Spring or Spring Boot web application, we should follow these steps:
- Create a new test class.
- Add a
mockMvcTesterfield to the test class and set the type of this field toMockMvcTester. - Create a setup method that's run before a test method is run.
- Create a new
MockMvcobject by using the standalone configuration. - Create a new
MockMvcTesterobject by invoking thestatic create()method of theMockMvcTesterclass and pass the createdMockMvcobject as a method parameter. Store the created object in themockMvcTesterfield.
After we have configured MockMvcTester, the source code of our test class looks as follows:
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.springframework.test.web.servlet.assertj.MockMvcTester;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
@DisplayName("MockMvcTester configuration example")
class MockMvcTesterTest {
private MockMvcTester mockMvcTester;
@BeforeEach
void configureSystemUnderTest() {
var mockMvc = MockMvcBuilders.standaloneSetup(new TestController())
.build();
mockMvcTester = MockMvcTester.create(mockMvc);
}
}
2. If we are writing integration tests for a Spring web application, we should follow these steps:
- Create a new test class.
- Annotate the test class with the
@SpringJUnitWebConfigannotation and configure the application context configuration class which configures the application context of our Spring web application. This annotation ensures that the Spring application context is loaded before our integration tests are run. - Add a
privateandfinalmockMvcTesterfield to the test class and set the type of this field toMockMvcTester. - Add a new constructor to the test class and ensure that the Spring container provides the loaded
WebApplicationContextas a constructor argument. - Create a new
MockMvcTesterobject by invoking thestatic from()method of theMockMvcTesterclass and pass theWebApplicationContextobject as a method parameter. Store the created object in themockMvcTesterfield.
After we have configured MockMvcTester, the source code of our test class looks as follows:
import org.junit.jupiter.api.DisplayName;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig;
import org.springframework.test.web.servlet.assertj.MockMvcTester;
import org.springframework.web.context.WebApplicationContext;
@SpringJUnitWebConfig(ApplicationContextConfiguration.class)
@DisplayName("MockMvcTester configuration example")
class MockMvcTesterTest {
private final MockMvcTester mockMvcTester;
@Autowired
MockMvcTesterTest(WebApplicationContext webApplicationContext) {
this.mockMvcTester = MockMvcTester.from(webApplicationContext);
}
}
3. If we are writing integration tests for a Spring Boot web application, we should follow these steps:
- Create a new test class.
- Annotate the test class with the
@SpringBootTestannotation. This annotation ensures that the Spring application context is loaded before our integration tests are run. - Annote the test class with the
@AutoConfigureMockMvcannotation. This enables the auto-configuration ofMockMvc. If the AssertJ library is found from the classpath, this annotation enables the auto-configuration ofMockMvcTesteras well. - Add a
privateandfinalmockMvcTesterfield to the test class and set the type of this field toMockMvcTester. - Inject the auto-configured
MockMvcTesterobject into themockMvcTesterfield by using constructor injection.
After we have configured MockMvcTester, the source code of our test class looks as follows:
import org.junit.jupiter.api.DisplayName;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.assertj.MockMvcTester;
@SpringBootTest
@AutoConfigureMockMvc
@DisplayName("MockMvcTester configuration example")
class MockMvcTesterTest {
private final MockMvcTester mockMvcTester;
@Autowired
MockMvcTesterTest(MockMvcTester mockMvcTester) {
this.mockMvcTester = mockMvcTester;
}
}
- Spring Framework Reference Manual: Configuring
MockMvcTester - The best way to configure
MockMvcwhen you are writing unit tests - The best way to configure
MockMvcwhen you are writing integration tests - How to configure
MockMvcby using the standalone configuration: "normal" controller methods and a REST API
We can now configure MockMvcTester. Let's move on and find out how we can send HTTP requests to the system under test.
Sending HTTP Requests to the System Under Test
When we want to send an HTTP request to the system under test by using MockMvcTester, we have to follow these steps:
- Create a new request builder object which allows us to specify the target URI.
- Specify the target URI by using our request builder object.
- Set the other properties of the HTTP request (such as content type, cookies, headers, query and form parameters, request body, and so on).
- Send the HTTP request to the system under test and obtain the result object which allows us to write assertions for the returned HTTP response with AssertJ.
MockMvc doesn't send real HTTP requests to the system under test. All tests are run in a mock environment provided by MockMvc.Let's move on and take a closer look at these steps one by one.
1. When we want to create a new MockMvcRequestBuilder object, we can use the following methods of the MockMvcTester class: delete(), get(), head(), method() (pass the preferred HttpMethod as a method parameter), options(), patch(), post(), and put().
For example, if we want to create a request builder object for a GET request, our code looks as follows:
mockMvcTester.get() //Specify the target URI //Configure the other properties of the HTTP request //Send the HTTP request and get the result object
On the other hand, we can also use the method() method of the MockMvcTester class. If we use this approach, our code looks as follows:
mockMvcTester.method(HttpMethod.GET) //Specify the target URI //Configure the other properties of the HTTP request //Send the HTTP request and get the result object
2. When we want to specify the target URI, we have to invoke the uri() method of the AbstractMockHttpServletRequestBuilder class. The AbstractMockHttpServletRequestBuilder class has two uri() methods, which take different method parameters. These method parameters are:
- A
java.net.URIobject which represents an absolute and fully constructed URI. - An URI template (a
Stringobject) and a sequence of objects which contain the URI variables.
Let's take a look at some examples which demonstrate how we can specify the target URI.
If we want to send a GET request to the path: /api/todo, our code looks as follows:
mockMvcTester.get()
.uri("/api/todo")
//Configure the other properties of the HTTP request
//Send the HTTP request and get the result object
If we want to send a GET request to the path: /api/todo/1, our code looks as follows:
mockMvcTester.get()
.uri("/api/todo/{id}", 1)
//Configure the other properties of the HTTP request
//Send the HTTP request and get the result object
If we want to specify query parameters by using the URI template style, our code looks as follows:
mockMvcTester.get()
.uri("/api/todo?searchTerm={searchTerm}", "foo")
//Configure the other properties of the HTTP request
//Send the HTTP request and get the result object
3. After we have specified the target URI by invoking the uri() method, we can configure the other properties (such as content type, cookies, headers, query and form parameters, request body, and so on) of the HTTP request that's send to the system under test by using the request builder that's returned by the uri() method.
Let's take a look at some examples which demonstrate how we can use the returned request builder object.
If we want to specify the content type, we have to invoke the contentType() method of the AbstractMockHttpServletRequestBuilder class and pass a org.springframework.http.MediaType object as a method parameter. If we want to set the content type to: application/json, our code looks as follows:
mockMvcTester.post()
.uri("/todo-item")
.contentType(MediaType.APPLICATION_JSON)
//Send the HTTP request and get the result object
If we want to add a cookie to the HTTP request, we have to invoke the cookie() method of the AbstractMockHttpServletRequestBuilder class and pass a jakarta.servlet.http.Cookie object as a method parameter. If we want to add a cookie with name: CookieName and value: CookieValue to the HTTP request, our code looks as follows:
mockMvcTester.get()
.uri("/api/todo")
.cookie(new Cookie("CookieName", "CookieValue"))
//Send the HTTP request and get the result object
If we want to add a header to the HTTP request, we have to invoke the header() method of the AbstractMockHttpServletRequestBuilder class and pass these method parameters to the invoked method:
- The name of the header
- One or more header values
If we want to set the content type to: application/json by using the header() method, our code looks as follows:
mockMvcTester.post()
.uri("/todo-item")
.header("Content-Type", "application/json")
//Send the HTTP request and get the result object
If we want to add a query or a form parameter to the HTTP request, we have to invoke the param() method of the AbstractMockHttpServletRequestBuilder class and pass these method parameters to the invoked method:
- The name of the parameter
- One or more parameter values
If we want to add a query parameter: Foo with the value: Bar to the HTTP request, our code looks as follows:
mockMvcTester.post()
.uri("/todo-item")
.param("Foo", "Bar")
//Send the HTTP request and get the result object
If we want to add a form parameter: Foo with the value: Bar to the HTTP request, our code looks as follows:
mockMvcTester.post()
.uri("/todo-item")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.param("Foo", "Bar")
//Send the HTTP request and get the result object
If we want to specify the request body of the HTTP request, we have to invoke the content() method of the AbstractMockHttpServletRequestBuilder class and pass the request body as a method parameter. If we want to add a JSON document to the request body, our code looks as follows:
mockMvcTester.post()
.uri("/todo-item")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"description": "This is description",
"name": "This is name"
}
""")
//Send the HTTP request and get the result object
4. After we have created the HTTP request, we can send it to the system under test by invoking the exchange() method of the MockMvcRequestBuilder class. This method returns a MockMvcResult object which allows us to write assertions for the returned HTTP response with AssertJ. If we want to send an HTTP request to the system under test and store the result object in a local variable called: result, our code looks as follows:
var result = mockMvcTester.post()
.uri("/todo-item")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"description": "This is description",
"name": "This is name"
}
""")
.exchange();
We can now send HTTP requests to the system under test. Next, we will learn to write assertions for the returned HTTP response.
Writing Assertions
When we want to write assertions for the returned HTTP response, we have to:
- Invoke the
static assertThat()method of theorg.assertj.core.api.Assertionsclass and pass theMvcTestResultobject as a method parameter. - Write assertions for the returned HTTP response by using the fluent assertion API that's provided by the
MvcTestResultAssertclass.
MvcTestResult object to the static assertThat() method because it implements the org.assertj.core.api.AssertProvider interface.For example, if we have to write assertions for an HTTP response that's returned by the API endpoint which returns the information of one todo item, we can use one of these two options:
1. Store the response in a variable
var result = mockMvcTester.get()
.uri("/todo-item/{id}", id)
.exchange();
assertThat(result)
//Specify expectation
2. Pass the result to the assertThat() method
assertThat(
mockMvcTester.get()
.uri("/todo-item/{id}", id)
.exchange()
)
//Specify expectation
We can specify our expectations by using the assertion methods provided by these classes:
- The
abstract AbstractHttpServletResponseAssertclass provides AssertJ assertion methods forHttpServletResponseobjects. These methods allow us to ensure that the system under test returns an HTTP response that has the correct content type, correct response headers, and correct HTTP status code. - The
abstact AbstractMockHttpServletResponseAssertclass extends theAbstractHttpServletResponseAssertclass and provides AssertJ assertion methods forMockHttpServletResponseobjects. The methods allow us to verify that the system under test returns an HTTP response that has the correct response body, contains the correct forwarded URL, contains the correct redirected URL, and contains the correct servlet error message. - The
MvcTestResultAssertclass extends theAbstractMockHttpServletResponseAssertclass and provides AssertJ assertion methods forMvcTestResultobjects. These methods allow us to ensure that the system under test returns an HTTP response that has correct cookies, sets the correct flash attributes, and returns the correctModelAndViewobject. Also, this class provides methods which allow us to print the content of the returnedMvcResultobject.
- The Javadoc of the
AbstractHttpServletResponseAssertclass - The Javadoc of the
AbstractMockHttpServletResponseAssertclass - The Javadoc of the
MvcTestResultAssertclass
There are two things I want to point out:
- I think that we should use the second option because using an extra variable adds unnecessary noise to our test methods.
- Because the required assertions depend on the tested controller method, I will cover the actual assertion methods in the next parts of my
MockMvcTestertutorial.
We are now familiar with the basic features of MockMvcTester. Let's summarize what we learned from this blog post.
Summary
This blog post has taught us five things:
MockMvcTesterintegratesMockMvcwith AssertJ.MockMvcTesterhelps us to clean up our test code and write better assertions for JSON documents.- When we want to configure
MockMvcTester, we can use the standalone configuration, create a newMockMvcTesterobject from the loaded SpringWebApplicationContext, or use the Spring Boot's auto-configuration support. - We can create an HTTP request and send it to the system under test by using the fluent API of the
MockMvcRequestBuilderclass. - When we want to write assertions for the returned HTTP response, we have to invoke the
static assertThat()method of theorg.assertj.core.api.Assertionsclass, pass theMvcTestResultobject as a method parameter, and write assertions by using the fluent assertion API that's provided by theMvcTestResultAssertclass.
P.S. You can get the example application from Github
Nicely written article! I love the step-by-step introduction of next configuration/usage options.
Thank you for the feedback! I am happy to hear that this blog post was useful to you.
Giving example covering all possible scenarios is very good
Fine news for all us