Backend

99 Ngày Java — Ngày 89: Mockito

SSite Admin
23 tháng 10, 2026 3 phút đọc 6 lượt xem
99 Ngày Java — Ngày 89: Mockito

Lớp cần test hiếm khi đứng một mình: nó gọi repository, cổng thanh toán, dịch vụ gửi mail. Mockito tạo đối tượng giả cho các phụ thuộc đó để unit test chạy nhanh, lặp lại được và chỉ kiểm tra đúng một lớp. Dùng đúng thì test rõ ràng; dùng quá tay thì test chỉ còn mô tả lại cài đặt.

Mockito

1. Mock, stub và verify

mock() tạo một đối tượng giả: mọi phương thức trả giá trị mặc định như null, 0 hoặc danh sách rỗng. Stub là dạy nó trả lời: when(...).thenReturn(...) hoặc thenThrow(...). verify kiểm tra một tương tác đã xảy ra. Hai việc này trả lời hai câu hỏi khác nhau: stub cấp dữ liệu đầu vào cho lớp cần test, verify kiểm tra tác động phụ mà lớp đó gây ra.

PaymentGateway gateway = mock(PaymentGateway.class);
when(gateway.charge("order-1", 500_000)).thenReturn(Receipt.paid("tx-9"));

var service = new CheckoutService(gateway);
var result = service.checkout("order-1", 500_000);

assertEquals("tx-9", result.transactionId());
verify(gateway).charge("order-1", 500_000);

2. @Mock và @InjectMocks

Với MockitoExtension, trường có @Mock được tạo mới cho từng test, và @InjectMocks tạo lớp cần test rồi truyền các mock vào constructor. Extension còn bật chế độ strict stubs: stub không được dùng tới sẽ làm test thất bại với UnnecessaryStubbingException, giúp test không tích tụ phần thừa. Nếu lớp có constructor rõ ràng, tự gọi new cũng tốt như @InjectMocks và dễ thấy phụ thuộc hơn.

@ExtendWith(MockitoExtension.class)
class CheckoutServiceTest {
    @Mock PaymentGateway gateway;
    @Mock OrderRepository orders;
    @InjectMocks CheckoutService service;

    @Test
    void marksOrderPaidWhenChargeSucceeds() {
        when(gateway.charge(anyString(), anyLong()))
            .thenReturn(Receipt.paid("tx-9"));

        service.checkout("order-1", 500_000);

        verify(orders).markPaid("order-1", "tx-9");
    }
}

3. ArgumentCaptor và matcher

Khi lớp cần test tự tạo một đối tượng rồi truyền cho phụ thuộc, bạn không có tham chiếu để so sánh. ArgumentCaptor bắt lại đối số đó để kiểm tra từng thuộc tính. Với matcher có một luật dễ quên: đã dùng any() hoặc eq() cho một tham số thì mọi tham số của lời gọi đó đều phải là matcher. Ưu tiên giá trị cụ thể; any() quá rộng làm test không còn bắt được lỗi truyền sai đối số.

@Captor ArgumentCaptor<Email> emailCaptor;

@Test
void sendsReceiptEmail() {
    service.checkout("order-1", 500_000);

    verify(mailer).send(emailCaptor.capture());
    Email email = emailCaptor.getValue();
    assertEquals("Receipt for order-1", email.subject());
}

4. Mock cái gì, không mock cái gì

Mock những ranh giới chậm hoặc không kiểm soát được: mạng, database, đồng hồ, dịch vụ ngoài. Không mock đối tượng giá trị, collection hay record; tạo đối tượng thật vừa ngắn vừa đúng hơn. Nếu một test cần năm sáu mock, đó thường là tín hiệu lớp đang ôm quá nhiều trách nhiệm chứ không phải lý do để mock thêm.

  • Ưu tiên kiểm tra kết quả trả về; chỉ verify tương tác khi tác động phụ chính là hành vi cần kiểm.

  • Tránh mock kiểu bạn không sở hữu; bọc nó sau một interface của bạn.

  • Dùng Clock truyền vào thay vì mock phương thức static lấy thời gian.

  • Truy vấn và ánh xạ database thật nên được kiểm bằng integration test.

Mock là công cụ cô lập, không phải mục tiêu. Test tốt vẫn là test nói rõ hành vi. Ngày 90 lùi lại một bước để nhìn cách tổ chức project và viết code sạch.

Tài liệu đối chiếu

  • https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html

  • https://site.mockito.org/

S

Site Admin

Engineer and writer. Building things with TypeScript and distributed systems.

Bình luận (0)

Bạn cần đăng nhập bằng Google để bình luận.

Hãy là người bình luận đầu tiên.

Bài viết liên quan

99 Ngày Spring — Ngày 89: Docker hóa Spring Boot

Dockerfile nhiều stage, layered jar để tận dụng cache, chạy không dùng root và buildpacks khi không muốn viết Dockerfile.

23 thg 10, 20263 phút7
99 Ngày Spring — Ngày 88: GraalVM Native Image

AOT processing, cách build native executable, giả định thế giới đóng và những hạn chế với reflection.

23 thg 10, 20263 phút5
99 Ngày Java — Ngày 88: Unit Test với JUnit 5

@Test và vòng đời, các assertion quan trọng, parameterized test và cách viết test đọc được như đặc tả.

23 thg 10, 20263 phút5