Nguyên tắc gốc: không để logic tự đọc đồng hồ hệ thống.
Tiêm một nguồn thời gian vào, test truyền thời điểm cố định.
const clock = { now: () => new Date('2026-03-01T00:05:00Z') }
const report = await buildDailyRevenue(clock, { timeZone: 'Asia/Ho_Chi_Minh' })
expect(report.date).toBe('2026-02-28')Tách bài toán thành ba phần test riêng:
1. Logic tính toán — hàm thuần nhận (khoảng thời gian, dữ liệu), không biết gì về cron. Đây là chỗ chứa gần hết test case.
2. Xác định khoảng thời gian — phần sai nhiều nhất. Job chạy lúc 00:05 giờ Việt Nam phải tổng hợp ngày hôm trước theo giờ Việt Nam, trong khi dữ liệu lưu bằng UTC. Các case cần có: giao dịch lúc 23:59 và 00:01 rơi vào đúng ngày; ngày cuối tháng; năm nhuận; nếu hệ thống phục vụ nhiều múi giờ thì cả chuyển giờ mùa (DST) — ngày có 23 hoặc 25 giờ.
3. Phần điều phối — kiểm bằng cách gọi trực tiếp entry point của job, không chờ cron kích hoạt. Ở đây test các tính chất vận hành:
- Chạy lại được: gọi job hai lần cho cùng một ngày không nhân đôi số liệu. Cần vì job lỗi thì người vận hành sẽ chạy tay lại.
- Chạy bù: job không chạy được hôm qua thì hôm nay xử lý được ngày bị thiếu.
- Nhiều instance: có nhiều pod thì cần khoá (advisory lock, hàng đợi) để chỉ một instance chạy — test bằng cách gọi song song hai lần và khẳng định chỉ một lần có hiệu lực.
Điều nên nói thêm: cấu hình cron cũng phải kiểm — chạy trong container múi giờ nào, và cron không phải là cơ chế đảm bảo, cần theo dõi "job đã chạy chưa" chứ không chỉ dựa vào lịch.