Chọn Testcontainers: mỗi lần chạy test, thư viện khởi động một container Postgres/MySQL/Redis thật qua Docker, cấp cổng ngẫu nhiên, chạy migration rồi dọn container khi xong.
const container = await new PostgreSqlContainer('postgres:16').start()
process.env.DATABASE_URL = container.getConnectionUri()
await runMigrations()
// ... tests ...
await container.stop()Vì sao không chọn hai phương án kia:
- SQLite in-memory nhanh nhưng khác dialect: không có jsonb, kiểu ràng buộc khác, hành vi transaction và index khác. Test xanh nhưng câu SQL thật vẫn có thể sai cú pháp hoặc sai ngữ nghĩa ở Postgres. Đây là dạng "test đúng thứ không phải cái chạy ở production".
- DB staging dùng chung gây xung đột: hai người chạy test cùng lúc là dữ liệu đè nhau, CI không chạy song song được, và không ai dám truncate bảng.
Điểm cần lưu ý khi trả lời: yêu cầu Docker phải có ở máy dev và trên CI runner (GitHub Actions runner Linux có sẵn); chi phí là thời gian khởi động container — giảm bằng cách tái sử dụng một container cho cả suite thay vì mỗi file test một container, và chỉ chạy nhóm integration test này ở job riêng trong pipeline.