はじめに
React のテストライブラリでは act()
や waitFor()
を使って非同期処理を適切に待つ必要があり、これが原因で何度もテストが通らないことがあります。備忘録として記事にまとめました。
テストの目的
- ユーザーがフォームに入力し、ボタンを押すと、学習記録のリストが 1 つ増えることを確認する
-
data-testid="record-item"
を持つ要素の数をカウントし、登録後に増えていることを期待する
// 初期のリスト数を取得
const initialRecords = (await screen.findAllByTestId("record-item")).length;
// フォーム入力 & 登録ボタンをクリック
await userEvent.type(screen.getByPlaceholderText("テキストを入力"), "React Testing");
await userEvent.type(screen.getByPlaceholderText("0"), "2");
await userEvent.click(screen.getByRole("button", { name: "登録" }));
// 追加後のリスト数をチェック
await waitFor(async () => {
const newRecords = (await screen.findAllByTestId("record-item")).length;
expect(newRecords).toBeGreaterThan(initialRecords);
});
発生したエラー
React Testing Library を使ってフォーム入力と登録ボタンの動作をテストしようとしたが、テストが record-item
を取得できずに失敗
エラーの原因
データの取得が完了する前にテストが進行してしまっていたことが原因。具体的には、
-
データ取得が完了する前に
record-item
を取得しようとしていた
→ API からデータが返るのを待つ前にfindAllByTestId("record-item")
を呼んでしまい、0 件のままexpect(newRecords).toBeGreaterThan(initialRecords);
が失敗 -
act()
で状態更新を待っていなかった
→ ボタンを押した後の画面の更新を待たずに次の処理が走ってしまい、テストが成功しない
修正
① act()
でユーザー操作をラップ
修正前
await userEvent.click(screen.getByRole("button", { name: "登録" }));
修正後
await act(async () => {
await userEvent.click(screen.getByRole("button", { name: "登録" }));
});
act()
を使うことで、React の状態更新を適切に処理できるように
② waitFor()
で record-item
の取得を待つ
修正前
const newRecords = (await screen.findAllByTestId("record-item")).length;
修正後
await waitFor(async () => {
const newRecords = (await screen.findAllByTestId("record-item")).length;
expect(newRecords).toBeGreaterThan(initialRecords);
});
waitFor()
を使うことで、データ取得が完了するまでテストが進まないようにする
まとめ
- 非同期処理を伴う UI の変化は
act()
でラップする - API からのデータ取得を待つときは
waitFor()
を使う - エラーが出たら
console.log()
でデータの流れを確認すると原因が見つかりやすい
React Testing Library で非同期テストを書くときは、act()
と waitFor()
を適切に使うことが重要
この方の記事もとても参考になりました
https://oisham.hatenablog.com/entry/2024/01/18/150850