A Practical Guide to Testing Spring Controllers With MockMvcTester
Spring Framework 6.2 introduced MockMvcTester to support writing AssertJ style assertions using AssertJ under the hood. If you’re using Spring Boot, the spring-boot-starter-test dependency transitively adds the most commonly used testing libraries such as mockito, assertj, json-path, jsonassert, etc. So, if you’re using Spring Boot 3.4.0 (which uses Spring framework 6.2) or any later version, […]

Spring Framework 6.2 introduced MockMvcTester
to support writing AssertJ style assertions using AssertJ under the hood.
If you’re using Spring Boot, the spring-boot-starter-test
dependency transitively adds the most commonly used testing libraries such as mockito
, assertj
, json-path
, jsonassert
, etc. So, if you’re using Spring Boot 3.4.0 (which uses Spring framework 6.2) or any later version, you don’t need to add any extra dependencies to use MockMvcTester
.
In this article, we’ll explore how you can use MockMvcTester
for different testing scenarios.
Getting started with MockMvcTester
MockMvcTester
is built on top of MockMvc
and provides AssertJ support for writing tests and asserting the result.
You can find the sample application code here.
Here is an example test written using MockMvc
:
import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; class UserRestControllerTests { @Autowired MockMvc mockMvc; @Test void getUserByIdSuccessful() throws Exception { mockMvc.perform(get("/api/users/1")).andExpect(status().isOk()); } }
The same test written using the MockMvcTester
fluent API:
import org.springframework.test.web.servlet.assertj.MockMvcTester; import static org.assertj.core.api.Assertions.assertThat; class UserRestControllerTests { @Autowired MockMvcTester mockMvcTester; @Test void getUserByIdSuccessful() { assertThat(mockMvcTester.get().uri("/api/users/1")).hasStatusOk(); } }
It is much easier to use the MockMvcTester
fluent API rather than using the IDE feature to find the static imports for get()
, post()
, status()
, etc.
How to configure MockMvcTester?
If you’re writing a slice test for a controller using @WebMvcTest
, you can simply inject MockMvcTester
.
The @WebMvcTest
annotation is meta-annotated with @AutoConfigureMockMvc
, so a MockMvc
instance is auto-configured. If AssertJ is available, then a MockMvcTester
instance will also be auto-configured.
@WebMvcTest(controllers = UserRestController.class) class UserRestControllerTests { @Autowired MockMvcTester mockMvcTester; //... }
If you’re writing an integration test using @SpringBootTest
, then you need to add the @AutoConfigureMockMvc
annotation to the test class, and you can also inject MockMvcTester
.
@SpringBootTest @AutoConfigureMockMvc class UserRestControllerTests { @Autowired MockMvcTester mockMvcTester; //... }
If you’re already using MockMvc
, then you can gradually adopt MockMvcTester
by creating a MockMvcTester
instance from MockMvc
:
class UserRestControllerTests { @Autowired MockMvc mockMvc; MockMvcTester mockMvcTester; @PostConstruct void setUp() { mockMvcTester = MockMvcTester.create(mockMvc); } //... }
Writing tests using MockMvcTester
Let’s explore how we can write tests using MockMvcTester
in various scenarios.
Testing REST API JSON response
Assume we have a REST API endpoint for user registration that returns HTTP code 201 when successful, along with the response JSON payload with name
, email
, and role
properties.
We can write a test using MockMvcTester
:
import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.assertj.MockMvcTester; import org.springframework.test.web.servlet.assertj.MvcTestResult; import static org.assertj.core.api.Assertions.assertThat; @Test void userRegistrationSuccessful() { String requestBody = """ { "email": "siva@gmail.com", "password": "secret", "name": "Siva" } """; assertThat(mockMvcTester .post() .uri("/api/users") .contentType(MediaType.APPLICATION_JSON) .content(requestBody)) .hasStatus(HttpStatus.CREATED) .bodyJson() .isLenientlyEqualTo(""" { "name": "Siva", "email": "siva@gmail.com", "role": "ROLE_USER" } """); }
We have compared the HTTP status code, converted the response body to JSON, and compared it with our expected JSON structure.
We can split the code into two parts to execute the request and assert the result:
MvcTestResult testResult = mockMvcTester .post() .uri("/api/users") .contentType(MediaType.APPLICATION_JSON) .content(requestBody) .exchange(); assertThat(testResult) .hasStatus(HttpStatus.CREATED) .bodyJson() .isLenientlyEqualTo(""" { "name": "Siva", "email": "siva@gmail.com", "role": "ROLE_USER" } """);
So far, we have compared the response JSON with our expected JSON structure using a multiline string. Instead, we can also store the JSON as a classpath resource and compare them:
var expected = new ClassPathResource("/user-registration-response.json", UserRestControllerTests.class); assertThat(testResult) .hasStatus(HttpStatus.CREATED) .bodyJson() .isLenientlyEqualTo(expected);
If you need more control over the response body assertions, you can map the response into a Java object and assert it:
public record RegistrationResponse(String name, String email, String role) {} assertThat(testResult) .hasStatus(HttpStatus.CREATED) .bodyJson() .convertTo(RegistrationResponse.class) .satisfies(response -> { assertThat(response.name()).isEqualTo("Siva"); assertThat(response.email()).isEqualTo("siva@gmail.com"); assertThat(response.role()).isEqualTo("ROLE_USER"); });
Testing REST API exception handling scenarios
It is common practice to use @RestControllerAdvice
to handle exceptions centrally:
import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(UserAlreadyExistsException.class) public ResponseEntity
For user registration, we check the existence of the given email in the database and throw UserAlreadyExistsException
if the email already exists. The GlobalExceptionHandler
will handle this exception and return the appropriate response.
We can write a test to handle this scenario using MockMvcTester
:
@Test void shouldFailToRegisterWithExistingEmail() { String requestBody = """ { "email": "admin@gmail.com", "password": "secret", "name": "Administrator" } """; MvcTestResult testResult = mockMvcTester.post() .uri("/api/users") .contentType(MediaType.APPLICATION_JSON) .content(requestBody) .exchange(); assertThat(testResult) .failure() .isInstanceOf(UserAlreadyExistsException.class) .hasMessage("User with email admin@gmail.com already exists"); }
We have asserted that there is a failure with a specific exception type and the expected error message.
Testing the Thymeleaf view rendering controllers
We can write tests for controllers that handle the request and render a view, such as a Thymeleaf view template.
Let’s say we have a controller with two handler methods:
import jakarta.validation.Valid; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.servlet.mvc.support.RedirectAttributes; @Controller public class UserController { private final UserRepository userRepository; public UserController(UserRepository userRepository) { this.userRepository = userRepository; } @GetMapping("/users/{id}") public String getUserById(@PathVariable Long id, Model model) { var user = userRepository.findById(id); if (user != null) { model.addAttribute("user", user); return "user"; } return "not-found"; } @PostMapping("/users") public String createUser(@ModelAttribute("user") @Valid User user, BindingResult bindingResult, RedirectAttributes redirectAttributes) { userRepository.create(user); if (bindingResult.hasErrors()) { return "create-user"; } redirectAttributes.addFlashAttribute("successMessage", "User saved successfully"); return "redirect:/users"; } }
Let’s write the first test to invoke GET users/{id}
and assert the HTTP status code and the model data:
@Test void shouldGetUserById() { var result = mockMvcTester.get().uri("/users/1").exchange(); assertThat(result) .hasStatusOk() .hasViewName("user") .model() .containsKeys("user") .containsEntry("user", new User(1L, "Siva", "siva@gmail.com", "siva")); }
Here, we assert the expected view name, model attribute name to be user
, and the user object data.
Testing URL redirects and flash attributes
Let’s write a test to verify the successful scenario of creating a user with valid data:
@Test void shouldCreateUserSuccessfully() { var result = mockMvcTester.post().uri("/users") .contentType(MediaType.APPLICATION_FORM_URLENCODED) .param("name", "Test User 4") .param("email", "testuser4@gmail.com") .param("password", "testuser4") .exchange(); assertThat(result) .hasStatus(HttpStatus.FOUND) .hasRedirectedUrl("/users") .flash().containsKey("successMessage") .hasEntrySatisfying("successMessage", value -> assertThat(value).isEqualTo("User saved successfully")); }
We have submitted the form with valid data and asserted the expected behavior that the user will be redirected to the new URL /users
with a successMessage
flash attribute.
Testing model validation errors
When a form is submitted with invalid data, we usually redisplay the form with error messages.
Let’s see how we can test the form field validation errors:
@Test void shouldGetErrorsWhenUserDataIsInvalid() { var result = mockMvcTester.post().uri("/users") .contentType(MediaType.APPLICATION_FORM_URLENCODED) .param("name", "") // blank -invalid .param("email", "testuser4gmail.com") // invalid email format .param("password", "pwd") // valid .exchange(); assertThat(result) .model() .extractingBindingResult("user") .hasErrorsCount(2) .hasFieldErrors("name", "email"); }
Here, we have submitted the form with invalid values for the name
and email
fields and asserted the expected error details.
Similarly, you can assert the expected headers, cookies, multipart requests, etc.
Summary
As you have seen in this article, MockMvcTester
helps you to write tests using a fluent API and provides many custom assertions to verify the results more expressively.
To learn more about MockMvcTester
, you can check out the official documentation here.
If you’re using Spring Boot 3.4.0 or a later version, you can start using MockMvcTester
to write more expressive tests using a fluent API. If you’re using an older version, then MockMvcTester
could be a solid reason to consider upgrading!