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. loading state is visible synchronously
expect(screen.getByRole("status")).toBeInTheDocument()
// 2. data arrives: findBy retries until the element exists
expect(await screen.findByRole("listitem", { name: /dinh/i })).toBeInTheDocument()
// 3. loading indicator is gone
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à hợp đồng thật (vd chống gọi trùng) — đó là chi tiết cài đặt, refactor sang React Query là gãy.