Unit Testing Controller Layer in Spring Boot with JUnit 5 & Mockito

In our previous tutorial, we built a Spring Boot CRUD REST API for User Management using Spring Data JPA and MySQL. Then, we wrote unit tests for the Service Layer. Now, we will extend that tutorial by writing unit tests for the Controller Layer using JUnit 5, Mockito, and MockMvc.

The prerequisite for this tutorial is you need to first Build RESTful Web Services Using Spring Boot, Spring Data JPA, and MySQL

📌 Why Unit Testing for Controller?

✅ Ensures API endpoints return correct HTTP status codes.
✅ Verifies request validation & response structure.
Isolates controllers from database dependencies.
✅ Helps catch bugs before integration testing.

🚀 Step 1: Add Required Dependencies

📌 Ensure the following dependencies are present in pom.xml

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

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <scope>test</scope>
</dependency>

📌 Explanation

✔️ Spring Boot Test → Provides MockMvc for testing controllers.
✔️ Mockito → Used for mocking the Service Layer.
✔️ Jackson Databind → Serializes objects to JSON for request testing.

🚀 Step 2: Mock Dependencies in Controller Layer

Since we don’t want to interact with the database, we will mock the Service Layer and test only the controller logic.

📌 Create UserControllerTest.java inside net.javaguides.usermanagement.controller

package net.javaguides.usermanagement.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import net.javaguides.usermanagement.dto.UserDto;
import net.javaguides.usermanagement.exception.ResourceNotFoundException;
import net.javaguides.usermanagement.service.UserService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;

import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Mock
    private UserService userService;

    @InjectMocks
    private UserController userController;

    private UserDto userDto;

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);

        userDto = new UserDto(
            1L,
            "Ravi",
            "Kumar",
            "ravi.kumar@example.com",
            LocalDate.of(1995, 8, 15)
        );
    }
}

📌 Explanation

✔️ @WebMvcTest(UserController.class) → Loads only the UserController without the entire application.
✔️ @Mock → Mocks the UserService, preventing actual database interactions.
✔️ @InjectMocks → Injects the mock into UserController.
✔️ MockMvc → Simulates HTTP requests to the controller.
✔️ ObjectMapper → Converts Java objects to JSON format for request bodies.

🚀 Step 3: Write Unit Test for POST /api/users

📌 Add the following test inside UserControllerTest.java

@Test
void shouldCreateUser() throws Exception {
    when(userService.createUser(any(UserDto.class))).thenReturn(userDto);

    String json = new ObjectMapper()
            .registerModule(new JavaTimeModule())
            .writeValueAsString(userDto);

    mockMvc.perform(post("/api/users")
            .contentType(MediaType.APPLICATION_JSON)
            .content(json))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.id").value(1L))
            .andExpect(jsonPath("$.firstName").value("Ravi"))
            .andExpect(jsonPath("$.email").value("ravi.kumar@example.com"));

    verify(userService, times(1)).createUser(any(UserDto.class));
}

📌 Explanation

✔️ Mocks userService.createUser() to return a new user.
✔️ Sends a POST request with JSON data.
✔️ Verifies response status (200 OK) and response body.
✔️ Ensures createUser() is called exactly once.

🚀 Step 4: Write Unit Test for GET /api/users/{id}

📌 Add the following test inside UserControllerTest.java

@Test
void shouldGetUserById() throws Exception {
    when(userService.getUserById(1L)).thenReturn(userDto);

    mockMvc.perform(get("/api/users/1")
            .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.id").value(1L))
            .andExpect(jsonPath("$.firstName").value("Ravi"))
            .andExpect(jsonPath("$.email").value("ravi.kumar@example.com"));

    verify(userService, times(1)).getUserById(1L);
}

📌 Explanation

✔️ Mocks userService.getUserById(1L) to return a user.
✔️ Verifies response status (200 OK) and JSON data.
✔️ Ensures getUserById() is called once.

🚀 Step 5: Handle Exception for GET /api/users/{id}

📌 Add the following test inside UserControllerTest.java

@Test
void shouldReturnNotFoundWhenUserDoesNotExist() throws Exception {
    when(userService.getUserById(1L)).thenThrow(new ResourceNotFoundException("User not found with id: 1"));

    mockMvc.perform(get("/api/users/1")
            .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isNotFound());

    verify(userService, times(1)).getUserById(1L);
}

📌 Explanation

✔️ Mocks userService.getUserById(1L) to throw ResourceNotFoundException.
✔️ Verifies response status (404 Not Found).

🚀 Step 6: Write Unit Test for GET /api/users

📌 Add the following test inside UserControllerTest.java

@Test
void shouldGetAllUsers() throws Exception {
    UserDto user2 = new UserDto(
        2L,
        "Priya",
        "Sharma",
        "priya.sharma@example.com",
        LocalDate.of(1990, 5, 20)
    );

    List<UserDto> users = Arrays.asList(userDto, user2);

    when(userService.getAllUsers()).thenReturn(users);

    mockMvc.perform(get("/api/users")
            .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.size()").value(2));

    verify(userService, times(1)).getAllUsers();
}

🚀 Step 7: Write Unit Test for DELETE /api/users/{id}

📌 Add the following test inside UserControllerTest.java

@Test
void shouldDeleteUser() throws Exception {
    doNothing().when(userService).deleteUser(1L);

    mockMvc.perform(delete("/api/users/1")
            .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isNoContent());

    verify(userService, times(1)).deleteUser(1L);
}

🚀 Running the Tests

📌 Execute all the JUnit test cases by running the UserControllerTest class.

✅ All tests should pass successfully.
Unit Testing Controller Layer in Spring Boot with JUnit 5 & Mockito

🎯 Summary: What We Achieved

✔️ Mocked Service Layer using Mockito.
✔️ Tested all REST API endpoints.
✔️ Verified status codes and JSON responses.
✔️ Handled exception scenarios correctly.

🚀 Now your Spring Boot Controller Layer is fully tested with JUnit 5 & Mockito! 🎯

Comments

Spring Boot 3 Paid Course Published for Free
on my Java Guides YouTube Channel

Subscribe to my YouTube Channel (165K+ subscribers):
Java Guides Channel

Top 10 My Udemy Courses with Huge Discount:
Udemy Courses - Ramesh Fadatare