Skip to main content

Spring MockMVC

Introduction

When developing web applications with Spring MVC, testing your controllers is essential to ensure they behave as expected. However, testing controllers traditionally requires deploying your application to a server, which can be slow and cumbersome during development. This is where Spring's MockMVC comes in.

Spring MockMVC is a powerful testing framework that allows you to test your Spring MVC controllers without having to deploy them to a server. It simulates HTTP requests and responses, making it possible to test your controllers' behavior in isolation, without starting an actual HTTP server.

In this guide, you'll learn:

  • What MockMVC is and why it's useful
  • How to set up MockMVC in your project
  • How to write basic controller tests
  • Advanced techniques for testing complex scenarios

Prerequisites

Before diving into MockMVC, make sure you have:

  • Basic knowledge of Spring Framework
  • Understanding of Spring MVC concepts
  • Familiarity with JUnit or other testing frameworks
  • A Spring Boot project set up with Spring MVC

Setting Up MockMVC

Dependencies

First, ensure you have the necessary dependencies in your project. If you're using Spring Boot, these dependencies are typically included in spring-boot-starter-test:

xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

For Gradle:

groovy
testImplementation 'org.springframework.boot:spring-boot-starter-test'

Creating a MockMVC Instance

There are two main ways to set up MockMVC:

1. Standalone Setup

Standalone setup is used when you want to test controllers in isolation without loading the full Spring context:

java
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;

@ExtendWith(SpringExtension.class)
public class UserControllerTest {

private MockMvc mockMvc;

@InjectMocks
private UserController userController;

@Mock
private UserService userService;

@BeforeEach
public void setup() {
MockitoAnnotations.openMocks(this);
mockMvc = standaloneSetup(userController).build();
}

// Test methods...
}

2. Web Application Context Setup

This approach loads a complete application context, providing a more realistic test environment:

java
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;

@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTest {

@Autowired
private MockMvc mockMvc;

// Test methods...
}

With @AutoConfigureMockMvc, Spring Boot automatically configures the MockMvc instance for you.

Writing Basic MockMVC Tests

Now that you have MockMVC set up, let's write some tests for a simple user controller:

First, let's look at our controller:

java
@RestController
@RequestMapping("/api/users")
public class UserController {

private final UserService userService;

public UserController(UserService userService) {
this.userService = userService;
}

@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
return userService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}

@PostMapping
public ResponseEntity<User> createUser(@RequestBody @Valid User user) {
User savedUser = userService.save(user);
return ResponseEntity
.created(URI.create("/api/users/" + savedUser.getId()))
.body(savedUser);
}
}

Testing GET Requests

Here's how to test a GET request:

java
@Test
public void shouldReturnUserWhenUserExists() throws Exception {
// Given
User user = new User(1L, "John", "[email protected]");
when(userService.findById(1L)).thenReturn(Optional.of(user));

// When & Then
mockMvc.perform(get("/api/users/1")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.name").value("John"))
.andExpect(jsonPath("$.email").value("[email protected]"));
}

@Test
public void shouldReturnNotFoundWhenUserDoesNotExist() throws Exception {
// Given
when(userService.findById(99L)).thenReturn(Optional.empty());

// When & Then
mockMvc.perform(get("/api/users/99")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isNotFound());
}

Testing POST Requests

Here's how to test a POST request with a request body:

java
@Test
public void shouldCreateUserSuccessfully() throws Exception {
// Given
User userToCreate = new User(null, "Sarah", "[email protected]");
User savedUser = new User(1L, "Sarah", "[email protected]");

when(userService.save(any(User.class))).thenReturn(savedUser);

// When & Then
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"name\":\"Sarah\",\"email\":\"[email protected]\"}"))
.andExpect(status().isCreated())
.andExpect(header().string("Location", "/api/users/1"))
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.name").value("Sarah"))
.andExpect(jsonPath("$.email").value("[email protected]"));

// Verify that service was called
verify(userService).save(any(User.class));
}

Advanced MockMVC Techniques

Testing with Path Variables and Request Parameters

java
@Test
public void shouldFilterUsersByName() throws Exception {
// Given
List<User> matchingUsers = Arrays.asList(
new User(1L, "John Smith", "[email protected]"),
new User(2L, "Johnny Walker", "[email protected]")
);

when(userService.findByNameContaining("John")).thenReturn(matchingUsers);

// When & Then
mockMvc.perform(get("/api/users")
.param("name", "John")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(2)))
.andExpect(jsonPath("$[0].name").value("John Smith"))
.andExpect(jsonPath("$[1].name").value("Johnny Walker"));
}

Testing Exception Handling

Let's say our controller has exception handling for validation errors:

java
@Test
public void shouldReturn400WhenEmailIsInvalid() throws Exception {
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"name\":\"Sarah\",\"email\":\"invalid-email\"}"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.errors").isArray())
.andExpect(jsonPath("$.errors[*]", hasItem(containsString("valid email"))));
}

Testing with Authentication

To test endpoints that require authentication:

java
@Test
@WithMockUser(username = "admin", roles = {"ADMIN"})
public void shouldAllowAdminToDeleteUser() throws Exception {
mockMvc.perform(delete("/api/users/1")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isNoContent());

verify(userService).deleteById(1L);
}

@Test
@WithMockUser(username = "user", roles = {"USER"})
public void shouldForbidRegularUserFromDeletingUser() throws Exception {
mockMvc.perform(delete("/api/users/1")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isForbidden());

verify(userService, never()).deleteById(anyLong());
}

Debugging MockMVC Tests

When tests fail, it can be helpful to see exactly what was returned. You can use print() to output the response to the console:

java
mockMvc.perform(get("/api/users/1")
.contentType(MediaType.APPLICATION_JSON))
.andDo(print()) // Prints request and response details
.andExpect(status().isOk());

You can also log the JSON response for closer inspection:

java
MvcResult result = mockMvc.perform(get("/api/users/1")
.contentType(MediaType.APPLICATION_JSON))
.andReturn();

String responseContent = result.getResponse().getContentAsString();
System.out.println("Response: " + responseContent);

Real-World Example: Testing a Complete REST API

Let's build a more comprehensive test for a REST API that manages blog posts:

java
@SpringBootTest
@AutoConfigureMockMvc
public class BlogPostControllerIntegrationTest {

@Autowired
private MockMvc mockMvc;

@MockBean
private BlogPostService blogPostService;

@Test
public void shouldReturnBlogPostsWithPagination() throws Exception {
// Given
List<BlogPost> posts = List.of(
new BlogPost(1L, "Spring Testing", "A guide to testing Spring applications", "john", LocalDateTime.now()),
new BlogPost(2L, "MockMVC Tutorial", "Learn how to use MockMVC", "sarah", LocalDateTime.now())
);
Page<BlogPost> page = new PageImpl<>(posts, PageRequest.of(0, 10), 2);

when(blogPostService.findAll(any(Pageable.class))).thenReturn(page);

// When & Then
mockMvc.perform(get("/api/posts")
.param("page", "0")
.param("size", "10")
.param("sort", "createdAt,desc")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.content", hasSize(2)))
.andExpect(jsonPath("$.content[0].title").value("Spring Testing"))
.andExpect(jsonPath("$.content[1].title").value("MockMVC Tutorial"))
.andExpect(jsonPath("$.totalElements").value(2))
.andExpect(jsonPath("$.totalPages").value(1))
.andExpect(jsonPath("$.size").value(10))
.andExpect(jsonPath("$.number").value(0));
}

@Test
@WithMockUser(roles = "EDITOR")
public void shouldCreateAndReturnNewBlogPost() throws Exception {
// Given
BlogPost newPost = new BlogPost(null, "Testing with MockMVC", "Content here", "editor", null);
BlogPost savedPost = new BlogPost(3L, "Testing with MockMVC", "Content here", "editor", LocalDateTime.now());

when(blogPostService.create(any(BlogPost.class))).thenReturn(savedPost);

// When & Then
mockMvc.perform(post("/api/posts")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"title\":\"Testing with MockMVC\",\"content\":\"Content here\"}"))
.andExpect(status().isCreated())
.andExpect(header().string("Location", containsString("/api/posts/3")))
.andExpect(jsonPath("$.id").value(3))
.andExpect(jsonPath("$.title").value("Testing with MockMVC"))
.andExpect(jsonPath("$.author").value("editor"));

ArgumentCaptor<BlogPost> postCaptor = ArgumentCaptor.forClass(BlogPost.class);
verify(blogPostService).create(postCaptor.capture());
assertEquals("Testing with MockMVC", postCaptor.getValue().getTitle());
}
}

Best Practices for MockMVC Testing

  1. Test in Isolation: Use standalone setup for unit tests to test controllers in isolation.
  2. Use WebApplicationContext for Integration Tests: For integration tests, use the web application context setup to test with the full Spring context.
  3. Test Both Happy Path and Error Cases: Ensure you test both successful scenarios and error conditions.
  4. Verify Service Interactions: Use Mockito to verify that your controllers interact correctly with services.
  5. Test Security Constraints: If your API has security constraints, test with and without the required permissions.
  6. Use Appropriate Matchers: Utilize JSONPath expressions and Hamcrest matchers to validate response content accurately.
  7. Keep Tests Readable: Use descriptive test names and comments to make tests easy to understand.
  8. Test Complex Request Bodies: For controllers that accept complex JSON structures, test with various input combinations.

Common MockMVC Assertions

Here's a quick reference for commonly used MockMVC assertions:

java
// Status code assertions
.andExpect(status().isOk()) // 200
.andExpect(status().isCreated()) // 201
.andExpect(status().isNoContent()) // 204
.andExpect(status().isBadRequest()) // 400
.andExpect(status().isUnauthorized()) // 401
.andExpect(status().isForbidden()) // 403
.andExpect(status().isNotFound()) // 404

// Header assertions
.andExpect(header().string("Location", "/api/users/1"))
.andExpect(header().exists("ETag"))
.andExpect(header().doesNotExist("X-Custom-Header"))

// Content assertions
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(content().json("{'name':'John', 'age':30}"))
.andExpect(content().string("Expected response string"))

// JSON path assertions
.andExpect(jsonPath("$.name").value("John"))
.andExpect(jsonPath("$.age").value(30))
.andExpect(jsonPath("$.addresses").isArray())
.andExpect(jsonPath("$.addresses", hasSize(2)))
.andExpect(jsonPath("$.addresses[0].city").value("New York"))

Summary

Spring MockMVC is an invaluable tool for testing Spring MVC controllers without deploying your application to a server. It allows you to:

  • Simulate HTTP requests with different HTTP methods, headers, path variables, and request parameters
  • Assert various aspects of HTTP responses like status codes, headers, and response bodies
  • Test your controllers in isolation or within the Spring context
  • Validate both happy paths and error scenarios
  • Test security constraints and authentication requirements

By incorporating MockMVC into your testing strategy, you can ensure that your controllers behave as expected, leading to more reliable and robust Spring web applications.

Additional Resources

Exercises

  1. Write MockMVC tests for a controller that handles CRUD operations for a Product entity.
  2. Create tests for a controller that requires authentication and has different endpoints for different user roles.
  3. Test a controller that handles file uploads using MockMVC.
  4. Write tests for a controller with custom exception handling for various business rule violations.
  5. Create a test suite that validates pagination, sorting, and filtering for a REST API endpoint.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)