Ba mức, dùng cả ba ở các tầng khác nhau chứ không chọn một:
1. Test double ở tầng service (đa số case). Đưa client bên thứ ba ra sau một interface của mình, test tiêm bản giả. Ở đây kiểm những thứ thuộc về mình: gọi có đúng tham số không, gặp lỗi 500 thì retry hay đánh dấu thất bại, timeout thì đơn hàng ở trạng thái gì.
const gateway = { charge: vi.fn().mockRejectedValue(new TimeoutError()) }
await expect(checkout(order, gateway)).resolves.toMatchObject({ status: 'pending_verify' })2. HTTP-level stub cho tầng adapter. Chặn ở lớp HTTP (msw, WireMock) và trả về payload thật đã lưu lại. Cách này kiểm được phần hay sai nhất: parse JSON, đọc header chữ ký, ánh xạ mã lỗi của họ sang mã lỗi của mình.
3. Sandbox của nhà cung cấp — chạy riêng, không nằm trong CI mỗi commit. Đây là nơi duy nhất phát hiện việc họ đổi API. Chạy theo lịch (hằng đêm) và cho phép đỏ mà không chặn merge, vì sandbox có thể sập vì lý do ngoài tầm kiểm soát.
Record/replay (ghi lần đầu bằng dịch vụ thật, các lần sau phát lại từ file) là điểm trung gian tiện lợi, nhưng phải nhớ hai điều: xoá thông tin nhạy cảm (token, số thẻ) trước khi commit file ghi, và định kỳ ghi lại vì bản ghi cũ sẽ lệch dần so với API thật.
Điều then chốt: webhook cũng phải test. Nhiều cổng thanh toán trả kết quả qua webhook chứ không qua response — cần test riêng cho handler đó: chữ ký sai thì từ chối, cùng một webhook gửi hai lần không được cộng tiền hai lần.