はじめに
こんにちは。アメリカ在住で独学エンジニアを目指している Taira です。
React Testing Library を使っていると、
- 「
waitForとactってどう違うの?」 - 「どっちを使えばいいの?」
と迷うことがあると思います。
結論から言うと:
-
act= React の 副作用が終わってから確認するためのラッパー -
waitFor= 非同期処理が落ち着いて「最終状態」になるまで待つためのユーティリティ
この2つの違いをシンプルに整理します。
act とは
- React 公式が提供する低レベル API。
- **「状態更新と副作用が終わるまで待ってから次の処理に進む」**ことを保証します。
import { act } from "react-dom/test-utils";
act(() => {
counter.increment(); // setState
});
// 副作用が完了してから確認できる
expect(screen.getByText("count: 1")).toBeInTheDocument();
👉 副作用が完了した「あと」に確認するためのもの。
Testing Library の render や userEvent は内部で必要に応じて act を呼んでくれるので、通常はあまり意識しなくてOKです。
ただし 外部ストアの直接更新やフェイクタイマー進行などは自動で包まれないため、手動で act を書く必要があります。
waitFor とは
- Testing Library が提供するユーティリティ。
- 非同期処理で「最終的に UI がどうなるか」をリトライしながら待つ。
-
Promise を返すので
awaitが必須。テスト関数自体もasyncにする必要があります。
test("保存メッセージが表示される", async () => {
render(<SaveButton />);
await user.click(screen.getByRole("button", { name: "保存" }));
// await を忘れるとテストが正しく動かない
await waitFor(() => {
expect(screen.getByText("保存しました")).toBeInTheDocument();
});
});
👉 単純に「要素が現れるのを待つ」なら findBy* を優先し、
複雑な非同期処理をまたぐ場合にだけ waitFor を使うのがおすすめです。
使い分けの目安
-
ユーザー操作や要素の表示待ち →
findBy*/waitForElementToBeRemoved -
複雑な非同期処理で「最終状態」を待ちたい →
waitFor(必ずawaitを付ける) -
外部ストア更新やタイマー進行など、副作用が終わるのを待ちたい →
actを明示的に
具体例
waitFor を使うケース
test("カウントが5になる", async () => {
render(<Counter />);
await waitFor(() => {
expect(screen.getByRole("status")).toHaveTextContent("count: 5");
});
});
act を使うケース(タイマー)
test("debounce 入力", () => {
vi.useFakeTimers();
render(<DebouncedInput />);
user.type(screen.getByRole("textbox"), "hello");
// タイマー進行は act で包む
act(() => {
vi.runAllTimers();
});
expect(screen.getByText("value: hello")).toBeInTheDocument();
});
まとめ
-
act= React の副作用が終わるのを待ってから確認する -
waitFor= 非同期で UI が最終状態になるまでリトライして待つ-
awaitを付けるのを忘れない!テスト関数もasync必須
-
-
実際のテストでは
- まず
findBy*/userEventを使う - 必要に応じて
waitFor - それでも警告が出るときに
actを明示的に使う
- まず