Bài 11: Mockito + JUnit 5: Hướng Dẫn Viết Unit Test Cho Service Layer Step By Step

Mục tiêu
Sau bài này, bạn sẽ hiểu được:
- Unit Test là gì, và vì sao phải viết
- JUnit dùng để làm gì
- Mockito làm gì, và khi nào cần mock
- 3 câu thần chú:
when(...).thenReturn(...)assertEquals(...)verify(...)
1. Unit Test là gì?
Unit Test là việc kiểm thử từng đơn vị nhỏ nhất trong code, thường là một hàm hoặc một class.
Mục tiêu:
- đảm bảo mỗi hàm hoạt động đúng như mong đợi
- kiểm tra nhiều đầu vào khác nhau
- không phụ thuộc vào database thật, API thật, hay server thật
Ví dụ, giả sử ta có hàm:
UserService.getUserById(Long id)
Khi viết Unit Test, ta:
- không kết nối database thật
- không cần chạy ứng dụng Spring Boot
- chỉ kiểm tra logic bên trong hàm đó
2. JUnit là gì?
JUnit là framework testing phổ biến nhất cho Java.
Nó cung cấp các annotation như:
@Test-> đánh dấu một test case@BeforeEach-> chạy trước mỗi test@ExtendWith(MockitoExtension.class)-> kết hợp JUnit với Mockito
Ngoài ra còn có các hàm kiểm chứng như:
assertEquals()assertThrows()assertNotNull()
3. Mockito là gì?
Mockito giúp bạn tạo ra các đối tượng giả để test code mà không phụ thuộc vào hệ thống thật.
Ví dụ:
- thay vì kết nối DB thật, ta mock
UserRepository - mục tiêu là kiểm tra logic trong
UserServicemà không cần database
4. Ví dụ thực tế
Xem video:
https://youtu.be/29rGLS-aOB8
5. Giải thích chi tiết 3 “thần chú”
when(...).thenReturn(...)
Dùng để mô phỏng hành vi của mock object.
Ví dụ:
when(userRepository.findAll()).thenReturn(List.of(mockUser));
Nghĩa là:
- khi service gọi
findAll() - đừng đụng vào DB thật
- hãy trả về danh sách này
Đây là GIVEN phase: thiết lập dữ liệu và hành vi.
assertEquals(expected, actual)
Dùng để kiểm tra kết quả thực tế có đúng như mong đợi hay không.
assertEquals(1, result.size());
assertEquals("Alice", result.get(0).getName());
Ở đây:
1,"Alice"là expectedresult.size(),result.get(0).getName()là actual
Nếu khác nhau:
- test fail
- nghĩa là logic trong service đang sai
Đây là THEN phase: xác nhận đầu ra.
verify(mock, times(n))
Dùng để kiểm tra xem mock có được gọi đúng cách hay không.
verify(userRepository, times(1)).findAll();
Nghĩa là:
- đảm bảo
findAll()được gọi đúng 1 lần - nếu gọi nhiều hơn hoặc không gọi thì test fail
Đây là AND phase: xác nhận hành vi.
6. Biểu đồ luồng gọi

Giải thích luồng:
- test chạy và tạo mock
Repo - khi
getAllUsers()gọirepo.findAll(), mock trả về dữ liệu giả - test kiểm tra kết quả bằng
assertEquals - cuối cùng,
verify()đảm bảofindAll()thật sự được gọi
7. Khi nào cần mỗi cái?
| Câu lệnh | Dùng khi | Kiểm tra cái gì |
|---|---|---|
when(...).thenReturn(...) | muốn mô phỏng hành vi của dependency | mock data trả về |
assertEquals(...) | muốn so sánh kết quả đầu ra | logic đúng hay sai |
verify(...) | muốn xác nhận code có gọi đúng hàm phụ thuộc | hành vi |
Mẹo nhớ nhanh
-
Given -> When -> Then
- Given: setup dữ liệu, giả lập hành vi mock
- When: gọi method thật
- Then: kiểm tra kết quả và verify
-
1 test = 1 hành vi duy nhất
- đừng gộp nhiều logic vào 1 test
-
Tên test phải mô tả được hành vi
testGetUserById_UserExists()testGetUserById_UserNotFound_ShouldThrowException()
8. Kết luận
- JUnit là khung kiểm thử
- Mockito giúp mô phỏng phụ thuộc
- Unit Test chỉ test một logic duy nhất, không chạm vào DB, network, hay hệ thống ngoài
Ba bước vàng trong test:
- GIVEN ->
when(...) - WHEN -> gọi hàm thật
- THEN ->
assertEquals(...)vàverify(...)
9. Nâng cao với thenAnswer()
Trong Mockito, bạn có thể dùng thenAnswer để thực hiện một hành động tùy chỉnh.
Ví dụ:
- tự động gán
idcho đối tượngUserkhi nó được lưu lại - mô phỏng hành vi của repository hoặc database
Sử dụng lambda với thenAnswer()
thenAnswer() cho phép bạn tạo phản hồi linh hoạt dựa trên đối số thực tế được truyền vào method của mock.
Trước Java 8
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
when(mockedService.process(anyInt())).thenAnswer(new Answer<Integer>() {
public Integer answer(InvocationOnMock invocation) {
Integer argument = (Integer) invocation.getArguments()[0];
return argument * 2;
}
});
Với lambda của Java 8
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.when;
when(mockedService.process(anyInt())).thenAnswer(invocation -> {
Integer argument = invocation.getArgument(0);
return argument * 2;
});
invocation.getArgument(0) là phương thức dùng để lấy đối số đầu tiên đã được truyền vào method của mock.