Spring Boot
unit testing
controller endpoint
software development
Java

How to write a unit test for a Spring Boot Controller endpoint

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

For a Spring Boot controller, the fastest useful test is usually a web-slice test rather than a full application boot. The idea is to load the controller, mock the service layer, send an HTTP request through MockMvc, and verify the status code and response body.

Choose the Right Test Scope

A controller test is not the place to prove that JPA, Kafka, or the full application context works. If you want to verify only request mapping, validation, serialization, and HTTP behavior, use @WebMvcTest.

That gives you a focused test:

  • controller and MVC infrastructure are loaded
  • collaborators such as services are mocked
  • tests run much faster than @SpringBootTest

If you need the whole stack, use integration tests separately. Do not turn every controller test into a full application startup.

Example Controller

Here is a small controller that returns a user by id:

java
1import org.springframework.http.ResponseEntity;
2import org.springframework.web.bind.annotation.GetMapping;
3import org.springframework.web.bind.annotation.PathVariable;
4import org.springframework.web.bind.annotation.RequestMapping;
5import org.springframework.web.bind.annotation.RestController;
6
7@RestController
8@RequestMapping("/users")
9class UserController {
10    private final UserService userService;
11
12    UserController(UserService userService) {
13        this.userService = userService;
14    }
15
16    @GetMapping("/{id}")
17    ResponseEntity<UserDto> getUser(@PathVariable long id) {
18        UserDto user = userService.findById(id);
19        return user == null ? ResponseEntity.notFound().build() : ResponseEntity.ok(user);
20    }
21}
22
23record UserDto(long id, String name) {}
24
25interface UserService {
26    UserDto findById(long id);
27}

The controller depends only on UserService, which makes it easy to mock.

Write the Test With MockMvc

Now test the endpoint behavior:

java
1import static org.mockito.BDDMockito.given;
2import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
3import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
4import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
5
6import org.junit.jupiter.api.Test;
7import org.springframework.beans.factory.annotation.Autowired;
8import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
9import org.springframework.boot.test.mock.mockito.MockBean;
10import org.springframework.test.web.servlet.MockMvc;
11
12@WebMvcTest(UserController.class)
13class UserControllerTest {
14
15    @Autowired
16    private MockMvc mockMvc;
17
18    @MockBean
19    private UserService userService;
20
21    @Test
22    void returnsUserWhenFound() throws Exception {
23        given(userService.findById(1L)).willReturn(new UserDto(1L, "Ava"));
24
25        mockMvc.perform(get("/users/1"))
26                .andExpect(status().isOk())
27                .andExpect(jsonPath("$.id").value(1))
28                .andExpect(jsonPath("$.name").value("Ava"));
29    }
30
31    @Test
32    void returns404WhenMissing() throws Exception {
33        given(userService.findById(99L)).willReturn(null);
34
35        mockMvc.perform(get("/users/99"))
36                .andExpect(status().isNotFound());
37    }
38}

This test checks routing, JSON serialization, and status handling without bringing in the full application.

What to Assert

A good controller test normally verifies:

  • HTTP status code
  • response JSON or body text
  • headers when they matter
  • input validation behavior
  • whether the controller delegates correctly to the service

It usually should not assert internal implementation details that belong to the service or repository layers.

When to Use SpringBootTest Instead

If your endpoint behavior depends on security filters, custom Jackson configuration, real database state, or multiple layers working together, @WebMvcTest may be too narrow. In that case, write an integration test with @SpringBootTest and possibly @AutoConfigureMockMvc.

The important point is separation. Use focused controller tests for controller behavior and a smaller number of slower integration tests for end-to-end wiring.

For endpoints that accept request bodies, add tests for malformed JSON and validation failures as well. A controller is often responsible for returning the correct 400 response when input is syntactically valid HTTP but semantically invalid for the API contract.

Common Pitfalls

A common mistake is mocking too much. If the controller test is full of mocked HTTP details, you are no longer testing the controller; you are testing your own mocks.

Another mistake is using @SpringBootTest for every endpoint test. That slows the suite down and hides the controller's actual responsibility.

A third mistake is forgetting JSON assertions. A 200 OK response alone does not prove that the endpoint returned the correct data shape.

Summary

  • Use @WebMvcTest and MockMvc for focused Spring Boot controller tests.
  • Mock the service layer with @MockBean so the controller can be tested in isolation.
  • Assert status codes, JSON content, and validation behavior.
  • Reserve @SpringBootTest for full-stack integration scenarios.
  • Keep controller tests narrow so they stay fast and easy to diagnose.

Course illustration
Course illustration

All Rights Reserved.