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
MockMvcTester
is 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
MockMvc
when 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 theJSONAssert
class. - We must remember to pass
true
as the last argument of theassertEquals()
method if we want that the assertion uses strict mode andfalse
if 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
mockMvcTester
field 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
MockMvc
object by using the standalone configuration. - Create a new
MockMvcTester
object by invoking thestatic create()
method of theMockMvcTester
class and pass the createdMockMvc
object as a method parameter. Store the created object in themockMvcTester
field.
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
@SpringJUnitWebConfig
annotation 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
private
andfinal
mockMvcTester
field 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
WebApplicationContext
as a constructor argument. - Create a new
MockMvcTester
object by invoking thestatic from()
method of theMockMvcTester
class and pass theWebApplicationContext
object as a method parameter. Store the created object in themockMvcTester
field.
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
@SpringBootTest
annotation. This annotation ensures that the Spring application context is loaded before our integration tests are run. - Annote the test class with the
@AutoConfigureMockMvc
annotation. This enables the auto-configuration ofMockMvc
. If the AssertJ library is found from the classpath, this annotation enables the auto-configuration ofMockMvcTester
as well. - Add a
private
andfinal
mockMvcTester
field to the test class and set the type of this field toMockMvcTester
. - Inject the auto-configured
MockMvcTester
object into themockMvcTester
field 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
MockMvc
when you are writing unit tests - The best way to configure
MockMvc
when you are writing integration tests - How to configure
MockMvc
by 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.URI
object which represents an absolute and fully constructed URI. - An URI template (a
String
object) 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.Assertions
class and pass theMvcTestResult
object as a method parameter. - Write assertions for the returned HTTP response by using the fluent assertion API that's provided by the
MvcTestResultAssert
class.
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 AbstractHttpServletResponseAssert
class provides AssertJ assertion methods forHttpServletResponse
objects. 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 AbstractMockHttpServletResponseAssert
class extends theAbstractHttpServletResponseAssert
class and provides AssertJ assertion methods forMockHttpServletResponse
objects. 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
MvcTestResultAssert
class extends theAbstractMockHttpServletResponseAssert
class and provides AssertJ assertion methods forMvcTestResult
objects. 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 correctModelAndView
object. Also, this class provides methods which allow us to print the content of the returnedMvcResult
object.
- The Javadoc of the
AbstractHttpServletResponseAssert
class - The Javadoc of the
AbstractMockHttpServletResponseAssert
class - The Javadoc of the
MvcTestResultAssert
class
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
MockMvcTester
tutorial.
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:
MockMvcTester
integratesMockMvc
with AssertJ.MockMvcTester
helps 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 newMockMvcTester
object 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
MockMvcRequestBuilder
class. - When we want to write assertions for the returned HTTP response, we have to invoke the
static assertThat()
method of theorg.assertj.core.api.Assertions
class, pass theMvcTestResult
object as a method parameter, and write assertions by using the fluent assertion API that's provided by theMvcTestResultAssert
class.
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