Unit Test với JUnit 5 + Mockito và Tư duy Kiểm thử — Ôn tập Fresher Backend VPBank

Vì sao mảng này quan trọng ở ngân hàng: code backend ngân hàng là code "chạm tiền". Một bug ở hàm chuyển tiền lọt ra production có thể gây sai lệch số dư hàng loạt, chi phí khắc phục (đối soát, hoàn tiền, báo cáo NHNN, uy tín) đắt gấp hàng trăm lần so với việc bắt được nó ở tầng test. Vì vậy ở VPBank và các ngân hàng nói chung, viết test không phải việc phụ mà là yêu cầu chất lượng/compliance bắt buộc — code không có test thường không được merge. Interviewer hỏi về test không phải để kiểm tra bạn thuộc API của JUnit, mà để xem bạn có tư duy phòng thủ với tiền của khách hàng hay không.


1. Danh sách chủ đề con theo độ ưu tiên

ƯU TIÊN CAO (bắt buộc nắm chắc, gần như chắc chắn bị hỏi)

ƯU TIÊN TRUNG BÌNH (nắm khái niệm, trả lời được 3–4 câu)

ƯU TIÊN THẤP (chỉ cần biết tên, nghe hỏi không bị "đứng hình")


2. Câu hỏi phỏng vấn khả năng cao + hướng trả lời

Câu 1. Unit test là gì? Khác integration test thế nào? (gần như chắc chắn hỏi)

Câu 2. Trong đồ án, em test code như thế nào? (câu hỏi thực chiến — trả lời trung thực)

Nếu đã viết test (mẫu): "Đồ án của em có khoảng X unit test cho tầng service, dùng JUnit 5 + Mockito, mock repository để test logic nghiệp vụ như [ví dụ cụ thể: tính tiền, kiểm tra số dư]. Em ưu tiên test phần logic tiền bạc/nghiệp vụ chính vì đó là nơi sai thì hậu quả lớn nhất. Em chạy test bằng mvn test trước mỗi lần commit."

Nếu chưa viết test (mẫu — trung thực nhưng không mất điểm): "Thời điểm làm đồ án em chủ yếu test thủ công qua Postman theo từng luồng nghiệp vụ, và em thừa nhận đó là thiếu sót. Sau khi tìm hiểu quy trình làm việc thực tế, em đã tự học JUnit 5 và Mockito, quay lại viết bổ sung một số unit test cho phần [chuyển tiền/đặt hàng] của đồ án — ví dụ case số dư không đủ phải ném exception. Em hiểu ở môi trường ngân hàng, code không có test là code chưa hoàn thành."

Câu 3. Mock là gì, tại sao cần? Test TransferService mà không cần DB thật thì làm sao?

// Giả định: TransferService(AccountRepository repo) — constructor injection
@ExtendWith(MockitoExtension.class)
class TransferServiceTest {

    @Mock
    private AccountRepository accountRepository;

    @InjectMocks
    private TransferService transferService;

    private Account nguon;
    private Account dich;

    @BeforeEach
    void setUp() {
        nguon = new Account("ACC001", new BigDecimal("1000000"));
        dich  = new Account("ACC002", new BigDecimal("500000"));
    }

    @Test
    void transfer_duSoDu_truNguonCongDich() {
        // Given
        when(accountRepository.findByAccountNumber("ACC001")).thenReturn(Optional.of(nguon));
        when(accountRepository.findByAccountNumber("ACC002")).thenReturn(Optional.of(dich));
        // When
        transferService.transfer("ACC001", "ACC002", new BigDecimal("300000"));
        // Then
        assertEquals(new BigDecimal("700000"), nguon.getBalance());
        assertEquals(new BigDecimal("800000"), dich.getBalance());
        verify(accountRepository).save(nguon);
        verify(accountRepository).save(dich);
    }

    @Test
    void transfer_khongDuSoDu_nemException_khongLuuGi() {
        when(accountRepository.findByAccountNumber("ACC001")).thenReturn(Optional.of(nguon));
        when(accountRepository.findByAccountNumber("ACC002")).thenReturn(Optional.of(dich));

        assertThrows(InsufficientBalanceException.class,
            () -> transferService.transfer("ACC001", "ACC002", new BigDecimal("2000000")));

        verify(accountRepository, never()).save(any()); // tiền không được đụng vào
    }
}

Câu 4. Thiết kế test case cho hàm chuyển tiền transfer(from, to, amount) gồm những case nào? (câu luyện tư duy — interviewer bank cực thích)

Trả lời theo nhóm, đừng liệt kê lộn xộn:

Câu 5. Coverage 100% có nghĩa là code không có bug không?

Câu 6. Vì sao nên viết code dễ test (testable)? Code thế nào là dễ test?

Câu 7. @BeforeEach dùng làm gì? Vì sao mỗi test phải độc lập với nhau?

Câu 8. Phân biệt unit test thuần Mockito với @WebMvcTest@SpringBootTest? Khi nào dùng cái nào?

Câu 9. TDD là gì? Em nghĩ gì về việc viết test trước?

Câu 10. verify() trong Mockito dùng làm gì? Khác gì when...thenReturn?


3. Góc ngân hàng — nhấn xuyên suốt khi trả lời bất kỳ câu nào


4. Ghi chú chiến thuật trước buổi phỏng vấn

  1. Nếu đồ án chưa có test — viết bổ sung NGAY, trước buổi phỏng vấn. Chọn class quan trọng nhất (service xử lý tiền/đơn hàng/nghiệp vụ chính), viết 5–10 unit test với JUnit 5 + Mockito: 2–3 happy case, còn lại là edge case (null, âm, không tồn tại, không đủ điều kiện). Commit lên repo. Chỉ mất 1 buổi nhưng biến câu trả lời "em chưa viết test" thành "em đã nhận ra thiếu sót và bổ sung — đây là các case em cover" — khác biệt rất lớn về ấn tượng.
  2. Chủ động lồng chuyện test khi kể về project, đừng đợi bị hỏi. Mẫu câu: "Phần khó nhất là logic X; để chắc nó đúng, em viết unit test cover các case A, B, C — nhờ đó khi refactor sang Y em phát hiện ngay một bug ở case biên." Kể được một bug cụ thể mà test đã bắt là ăn điểm gấp đôi.
  3. Chuẩn bị chạy lại được test trước mặt interviewer (nếu phỏng vấn có laptop/live coding): mvn test phải xanh; nhớ tên vài test method của chính mình.
  4. Thuộc lòng bộ khung 4 dòng để trả lời nhanh mọi câu hỏi viết test: @Mock repository → @InjectMocks service → when(...).thenReturn(...) → assert + verify. Cộng thêm assertThrows cho case lỗi là đủ xử lý 90% câu hỏi thực hành mức fresher.
  5. Câu chốt an toàn khi bị hỏi vượt tầm (Testcontainers, mutation testing...): "Em mới biết khái niệm ở mức đọc qua — [nói đúng 1 câu định nghĩa] — em chưa dùng thực tế nhưng rất muốn được học khi vào team." Trung thực + ham học luôn tốt hơn chém gió ở vòng phỏng vấn ngân hàng.