Chặn ở tầng network (MSW hoặc mock fetch), rồi assert theo những gì người dùng thấy ở từng giai đoạn.
it("renders the list after loading", async () => {
render(<UserList />)
// 1. trạng thái loading hiện ra ngay, đồng bộ
expect(screen.getByRole("status")).toBeInTheDocument()
// 2. dữ liệu về: findBy thử lại tới khi có element
expect(await screen.findByText(/dinh/i)).toBeInTheDocument()
// 3. chỉ báo loading đã biến mất
expect(screen.queryByRole("status")).not.toBeInTheDocument()
})Quy tắc chọn query trong luồng async:
- getBy cho thứ có ngay lập tức (skeleton, tiêu đề tĩnh).
- findBy cho thứ xuất hiện sau khi request resolve — nó là waitFor + getBy gói sẵn, có await.
- queryBy chỉ dùng khi assert không tồn tại, vì getBy sẽ ném lỗi ngay.
Hai case nữa nên có: rỗng (API trả mảng trống → hiện empty state) và lỗi (API trả 500 → hiện thông báo lỗi + nút thử lại). Đa số bug production nằm ở hai nhánh này chứ không phải happy path.
Đừng assert vào việc fetch được gọi bao nhiêu lần trừ khi đó là contract thật (vd chống gọi trùng) — đó là chi tiết implementation, refactor sang React Query là gãy.