Mockito là một framework mã nguồn mở để tạo các đối tượng mock trong kiểm thử đơn vị Java và Kotlin, cho phép cô lập mã được kiểm thử khỏi các phụ thuộc bên ngoài. Nhờ nó, nhà phát triển thay thế các kho lưu trữ thực, API client và cơ sở dữ liệu bằng các stub có kiểm soát với hành vi được xác định trước. Theo Mockito.org, thư viện được sử dụng trong hơn 60% các dự án Java có sử dụng kiểm thử đơn vị.
Các điểm chính
Mockito là một thư viện mã nguồn mở để tạo các đối tượng mock (stub) trong kiểm thử đơn vị cho Java, Kotlin và các ngôn ngữ JVM khác. Không giống JUnit, chịu trách nhiệm thực thi kiểm thử, Mockito giải quyết vấn đề cô lập — nó thay thế các phụ thuộc thực của lớp được kiểm thử bằng các đối tượng có thể dự đoán trước.
Không có mock, việc kiểm thử một phương thức truy cập cơ sở dữ liệu hoặc API bên ngoài đòi hỏi phải thiết lập môi trường thực — triển khai cơ sở dữ liệu, khởi động máy chủ. Mockito thay thế các phụ thuộc này bằng các đối tượng có hành vi cố định: phương thức repository.findById(1) luôn trả về một đối tượng User xác định mà không truy cập cơ sở dữ liệu.
Kiến trúc của Mockito dựa trên mẫu Proxy (cho các interface và lớp). Thư viện tạo một lớp con hoặc proxy cho kiểu được chỉ định và chặn tất cả các lời gọi phương thức, trả về các giá trị mặc định hoặc giá trị được đặt qua when().thenReturn().
Nguyên lý hoạt động của Mockito dựa trên ba thao tác cơ bản: tạo mock, cấu hình hành vi (stubbing) và xác minh lời gọi (verification). Mỗi thao tác sử dụng các phương thức tĩnh từ lớp org.mockito.Mockito — lớp được tải xuống nhiều nhất trong hệ sinh thái Java theo thống kê của Maven Central. Tất cả các lời gọi phương thức mock được ghi lại trong bộ nhớ, cho phép xác minh sau đó qua verify.
Một kiểm thử điển hình với Mockito bao gồm ba pha: Arrange — tạo mock và cấu hình stub qua when().thenReturn(), Act — gọi phương thức được kiểm thử, Assert — kiểm tra kết quả qua assertEquals và verify(mock). Cách tiếp cận này được gọi là AAA (Arrange-Act-Assert).
Hãy xem một kiểm thử đơn giản nơi Mockito thay thế repository người dùng. Phương thức when().thenReturn() cấu hình mock để lời gọi findById trả về một đối tượng User được chuẩn bị trước.
// Tạo mock repository
UserRepository mockRepo = mock(UserRepository.class);
// Cấu hình hành vi: findById(1) trả về người dùng
when(mockRepo.findById(1)).thenReturn(new User("Alice"));
// Kiểm tra rằng phương thức đã thực sự được gọi
User result = mockRepo.findById(1);
assertEquals("Alice", result.getName());
verify(mockRepo).findById(1);
Mockito cung cấp hai cách để tạo mock: phương thức tĩnh mock(Class) và annotation @Mock với khởi tạo qua MockitoAnnotations.openMocks(). Cách đầu tiên gọn nhẹ cho một hoặc hai mock, cách thứ hai thuận tiện khi có nhiều phụ thuộc — annotation làm giảm mã boilerplate.
Phương thức mock() nhận một lớp và trả về một đối tượng stub có thể được cấu hình qua when().thenReturn(). Tất cả các phương thức không được cấu hình trả về giá trị mặc định: 0 cho số, false cho boolean, null cho đối tượng.
ApiClient apiClient = mock(ApiClient.class);
Database database = mock(Database.class);
Annotation @Mock kết hợp với @ExtendWith(MockitoExtension.class) tự động tạo mock cho tất cả các trường của lớp kiểm thử. MockitoExtension chịu trách nhiệm khởi tạo trước mỗi kiểm thử.
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void getUserShouldReturnUserFromRepo() {
when(userRepository.findById(1)).thenReturn(new User("Alice"));
User result = userService.getUser(1);
assertEquals("Alice", result.getName());
}
}
Stubbing là quá trình xác định những gì một phương thức mock nên trả về khi được gọi với các đối số cụ thể. Cú pháp cơ bản: when(mock.method(args)).thenReturn(value). Cho các kịch bản khác nhau, Mockito cung cấp một số biến thể của phương thức then.
| Phương thức | Mục đích |
|---|---|
| thenReturn(value) | Luôn trả về giá trị đã chỉ định |
| thenThrow(exception) | Ném ngoại lệ khi được gọi |
| thenAnswer(answer) | Tính toán giá trị trả về một cách động |
| thenCallRealMethod() | Gọi phương thức thực (mock một phần) |
Khi giá trị trả về phụ thuộc vào đối số của lời gọi, thenAnswer được sử dụng với một lambda. Điều này hữu ích để mô phỏng làm việc với dữ liệu thực — ví dụ, tạo ID dựa trên đối tượng được truyền vào.
when(repository.save(any())).thenAnswer(invocation -> {
User user = invocation.getArgument(0);
user.setId(42);
return user;
});
Verify là một tính năng độc đáo của Mockito mà các thư viện đối tượng mock cũ hơn (EasyMock, jMock) không cung cấp.
Verify làm cho kiểm thử đáng tin cậy hơn vì nó kiểm tra không chỉ giá trị trả về mà còn cả các tác dụng phụ — lời gọi đến các phương thức không trả về kết quả (phương thức void). Phương thức verify(mock).methodName(args) kiểm tra xem một phương thức mock cụ thể có được gọi với các đối số đã chỉ định hay không. Điều này cho phép kiểm tra không chỉ kết quả mà còn cả quá trình — thực tế truy cập vào phụ thuộc.
Theo mặc định, verify kiểm tra rằng phương thức đã được gọi đúng một lần. Nếu cần số lượng khác, times(n), atLeast(n), never() và các bộ sửa đổi khác từ lớp Mockito được sử dụng.
// Kiểm tra số lượng lời gọi
verify(repository, times(3)).save(any());
verify(repository, never()).delete(any());
verify(repository, atLeastOnce()).findById(1);
// Kiểm tra thứ tự lời gọi
InOrder inOrder = inOrder(repository);
inOrder.verify(repository).save(any());
inOrder.verify(repository).flush();
Khi cần kiểm tra chính xác đối tượng nào đã được gọi cùng với một phương thức, ArgumentCaptor được sử dụng. Nó chụp giá trị đối số trong quá trình gọi và cho phép kiểm tra các trường của nó một cách riêng lẻ. ArgumentCaptor đặc biệt hữu ích khi mã được kiểm thử tạo một đối tượng bên trong và truyền nó cho một phụ thuộc — bạn không thể kiểm tra đối tượng đó bằng cách khác.
ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);
verify(repository).save(captor.capture());
assertEquals("Alice", captor.getValue().getName());
@Mock và @InjectMocks là hai annotation chính của Mockito giúp giảm đáng kể mã boilerplate. @Mock tạo mock cho một trường, và @InjectMocks tiêm tất cả các mock từ lớp kiểm thử vào đối tượng được kiểm thử thông qua constructor, setter hoặc trường.
Cơ chế @InjectMocks cố gắng tiêm các phụ thuộc theo thứ tự sau: constructor có nhiều đối số nhất, setter theo kiểu, trường riêng tư. Nếu không có phương pháp nào hoạt động, đối tượng sẽ giữ các phụ thuộc null và kiểm thử sẽ thất bại với NullPointerException.
Cần hiểu rằng: @InjectMocks không phân tích kiểu của trường — nó thay thế bất kỳ mock nào tương thích theo kiểu. Nếu một lớp có hai trường cùng kiểu, Mockito có thể tiêm nhầm mock. Trong những trường hợp như vậy, nên sử dụng constructor rõ ràng với các tham số mock.
Trong phát triển Android, Mockito được sử dụng cùng với JUnit để kiểm thử ViewModel, Repository và UseCase. Vì các lớp này chạy trên JVM mà không có ngữ cảnh Android, Mockito thay thế các phụ thuộc của chúng — Room DAO, Retrofit API, SharedPreferences — bằng các stub có hành vi có thể dự đoán trước.
Để thêm Mockito vào dự án Android, chỉ cần thêm phụ thuộc mockito-core hoặc mockito-inline (cái sau hỗ trợ mock các lớp final và phương thức tĩnh). Phiên bản 5.12.0 (2024) bao gồm hỗ trợ Java 21 và tích hợp JUnit 5 được cải thiện.
// build.gradle.kts (module)
dependencies {
testImplementation("org.mockito:mockito-core:5.12.0")
testImplementation("org.mockito:mockito-junit-jupiter:5.12.0")
}
Trước đây, việc mock các phương thức tĩnh và constructor yêu cầu PowerMock — một phần mở rộng hoạt động thông qua bytecode instrumentation. Bắt đầu từ Mockito 5.x với mockito-inline, khả năng này được tích hợp trực tiếp: mockStatic(ClassName.class) cho phép mock các phương thức tĩnh mà không cần thư viện bổ sung.
Một kịch bản điển hình: một ViewModel gọi một phương thức repository và biến đổi kết quả thành trạng thái UI. Mockito thay thế repository, và kiểm thử xác minh rằng ViewModel xử lý chính xác cả phản hồi thành công và lỗi. Khi sử dụng Clean Architecture, các mock được tạo cho từng lớp: DataSource, Repository và UseCase — điều này cho phép kiểm thử từng lớp một cách cô lập.
Câu hỏi thường gặp
Mockito là thư viện cho Java và Kotlin sử dụng proxy và phản xạ. MockK là thư viện Kotlin-first hỗ trợ coroutine, hàm mở rộng và lớp final mà không cần cấu hình thêm.
Bắt đầu từ Mockito 2.1, việc mock các lớp final được hỗ trợ thông qua opt-in. Trong phiên bản 5.x (mockito-inline), tính năng này được bật theo mặc định. Chỉ cần thêm phụ thuộc mockito-inline và sử dụng phương thức mock() tiêu chuẩn.
Spy là một mock một phần, theo mặc định gọi các phương thức thực nhưng cho phép ghi đè một số trong số chúng qua when().thenReturn(). Spy hữu ích để kiểm thử mã legacy khi không thể viết lại toàn bộ lớp.
thenReturn luôn trả về cùng một giá trị, bất kể đối số. thenAnswer tính toán giá trị trả về dựa trên lời gọi — đối số, bản thân mock và trạng thái. Đối với phản hồi động, luôn sử dụng thenAnswer.
Verify kiểm tra không chỉ kết quả mà còn cả quá trình — thực tế truy cập vào phụ thuộc. Điều này rất quan trọng đối với các dịch vụ cần lưu dữ liệu hoặc gửi thông báo. Không có verify, kiểm thử sẽ không phát hiện rằng một phương thức đã không gọi save() hoặc send().
Tóm tắt
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm