Nguyên tắc: không để test phụ thuộc đồng hồ thật. Có hai cách, ưu tiên cách một.
1. Tiêm thời gian vào như một phụ thuộc. Hàm nhận now (hoặc một clock) thay vì tự gọi Date.now(). Test truyền mốc thời gian cố định, không cần công cụ gì thêm.
ts
export function isExpired(token: Token, now: number) {
return token.expiresAt <= now
}
it('marks the token expired one second past its deadline', () => {
expect(isExpired({ expiresAt: 1_000 }, 1_001)).toBe(true)
})**2.
Fake timers** khi không sửa được chữ ký hàm, hoặc khi cần tua setTimeout/setInterval.
ts
beforeEach(() => vi.useFakeTimers({ now: new Date('2026-01-01T00:00:00Z') }))
afterEach(() => vi.useRealTimers())
it('retries after the backoff delay', async () => {
const promise = retryWithBackoff(fn)
await vi.advanceTimersByTimeAsync(5_000) // tua 5s thay vì chờ thật
await promise
})Hai bẫy hay gặp ở dự án VN: timezone (máy dev Asia/Ho_Chi_Minh, CI chạy UTC → test đỏ trên CI) — cố định TZ trong config test; và sleep thật trong test — mỗi chỗ vài giây, cả suite thành vài phút, đổi hết sang fake timers.