問題
React Testing Libraryを使用してテストを実装している中で、waitFor関数内で原因不明のエラーが出ていたのですが、コンソールログを入れるとうまくいったので、理由を調べてみました。
事象:
該当のコードがこちら。
afterEach(cleanup);
test("削除ボタンを押すと学習記録が削除される", async () => {
render(<App />);
// 削除ボタンが表示されるまで待機
await waitFor(() => {
const deleteButtons = screen.getAllByTestId("delete");
expect(deleteButtons.length).toBeGreaterThan(0);
// console.log("Initial delete buttons count:", deleteButtons.length);
});
// 初期の学習記録の数を取得
const initialRecords = screen.getAllByTestId("record");
// console.log("Initial records count:", initialRecords.length);
// 削除ボタンクリック
const deleteButton = screen.getAllByTestId("delete")[0];
fireEvent.click(deleteButton);
// console.log("Clicked delete button");
// 削除後の学習記録の数を検証
await waitFor(async () => {
const updatedRecords = await screen.getAllByTestId("record");
console.log("Initial records count after click:", initialRecords.length);
console.log("Updated records count:", updatedRecords.length);
expect(updatedRecords.length).toBe(initialRecords.length - 1);
});
});
上記コードの最後のwaitFor関数内にconsole.log
で変数の配列要素数をとってくるようにしました。
そうするとそれまでで続けていたどこが原因かわからないエラーがなくなり、テストが通りました。
解決の理由
どうしてconsole.log
を入れるとうまく行くのか謎だったのでさらに調べると、どうやらコンソールログが非同期の実行順序に影響を与えていた可能性が理由のようでした。
つまりconsole.log
を挿入することで、非同期の処理が完了するまでの時間が確保されてテストが正しく実行されたということです。
別の解決策
console.log
を入れる以外に方法はないか検索したところ、getAllByTestId
の代わりにfindAllByTestId
を使用すると同じ結果を得ることができました。
それぞれの違いを簡単に説明すると、
getAllByTestId
は同期的に動作して、指定されたテストIDに一致する要素を即座に返します。そして見つからない場合は、エラーを投げます。
findAllByTestId
は非同期的に動作をします。指定された時間内に要素が見つかるまで待機をして要素が見つかればそれを返します。
以下がコードです。
afterEach(cleanup);
test("削除ボタンを押すと学習記録が削除される", async () => {
render(<App />);
// 削除ボタンが表示されるまで待機
await waitFor(() => {
const deleteButtons = screen.getAllByTestId("delete");
expect(deleteButtons.length).toBeGreaterThan(0);
// console.log("Initial delete buttons count:", deleteButtons.length);
});
// 初期の学習記録の数を取得
const initialRecords = screen.getAllByTestId("record");
// console.log("Initial records count:", initialRecords.length);
// 削除ボタンクリック
const deleteButton = screen.getAllByTestId("delete")[0];
fireEvent.click(deleteButton);
// console.log("Clicked delete button");
// 削除後の学習記録の数を検証
await waitFor(
async () => {
await screen.findAllByTestId("record");
await new Promise((r) => setTimeout(r, 2000));
},
{ timeout: 3000 }
);
const updatedRecords = await screen.findAllByTestId("record");
console.log(updatedRecords.length);
expect(updatedRecords.length).toBe(initialRecords.length - 1);
});
今回はupdateRecordsの要素を取得するまでに時間がかかっているようで、findAllByTestIdで待機時間を指定して取得する方法でうまくいきました
上記ではawait new Promise((r) => setTimeout(r, 2000));
で2秒間待機をするように指定しています。
そして{ timeout: 3000 }
を設定することで、3秒以内にwaitFor関数の条件が満たされなかったらエラーをスローするようにしています。
おわりに
今回は何が原因でエラーが出ているのかわからず、ひたすらトライアンドエラーを繰り返していましたが、コンソールログをさまざまなところに設定することが解決の糸口につながりました。