はじめに
ローディング終了後のReactコンポーネント画面で特定の要素が存在するかテストするためfindByTestId()を使ったところUnable to find an element by xxxエラーが発生しました。
findBy~系のメソッドの仕様を把握できておらず解決に少し時間がかかったため、記事にしました。
問題
テスト対象は、マウント後に3秒ローディング画面を表示したあと、タイトルを表示するコンポーネントです。
import "./App.css";
import { useState, useEffect } from "react";
function App() {
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
(async () => {
// 3秒待機
await new Promise((resolve) => setTimeout(resolve, 3000));
setIsLoading(false);
})();
}, []);
// ローディング制御
if (isLoading) return <div>Loading</div>;
return <div data-testid="title">Hello World!</div>;
}
export default App;
上記コンポーネントのテスト用に作成したテストファイルは次の通りです。
import "@testing-library/jest-dom";
import { screen, render } from "@testing-library/react";
import App from "../App";
describe("JISOU課題テスト", () => {
it("タイトル表示チェック", async () => {
render(<App />);
const title = await screen.findByTestId("title");
expect(title).toBeInTheDocument();
});
});
上記テストファイルでテストすると、以下エラー(対象の要素が見つらない)が発生します。
Unable to find an element by: [data-testid="title"]
解決方法
findByTestId()のタイムアウト時間を変更すれば解決します。
import "@testing-library/jest-dom";
import { screen, render } from "@testing-library/react";
import App from "../App";
describe("JISOU課題テスト", () => {
it("タイトル表示チェック", async () => {
render(<App />);
const title = await screen.findByTestId("title",
+ undefined, // このundefinedも必要です!理由は後述します
+ {
+ timeout:5000
+ });
expect(title).toBeInTheDocument();
});
});
今回の原因
findBy~系のメソッドは要素が見つかるまで待機をしてくれますが、デフォルトでは1秒しか待機してくれません。テスト対象のローディングは3秒かかるので、ローディングが終わる前にfindByTestId()がタイムアウトしてしまったのが原因です。
そのため、findByTestId()の第3引数に、待機時間を5秒にするオプションを追加しました。
第2引数のundefinedの意味
第2引数にundefinedを指定した理由は、待機時間以外のオプションはデフォルトのままにしたかったからです。
わかりにくいと思うのでもう少し補足します。
findByTestId()の型情報は次の通りです。
findByTestId<HTMLElement>(
id: Matcher, // 第1引数
options?: MatcherOptions | undefined, // 第2引数
waitForElementOptions?: waitForOptions | undefined // 第3引数
): Promise<HTMLElement> (+1 overload)
つまりfindBy~メソッドで変更できるオプションはMatcherOptions(第2引数)とwaitForOptions(第3引数)の2種類あるということです。
待機時間などのオプションはwaitForOptions(第3引数)が保持しています。
export interface waitForOptions {
container?: HTMLElement
timeout?: number // ←これが待機時間です!
interval?: number
onTimeout?: (error: Error) => Error
mutationObserverOptions?: MutationObserverInit
}
今回MatcherOptions(第2引数)は変更せず、waitForOptions(第3引数)だけを変更するため、findByTestId()の第2引数にundefindを記述していました。
おわりに
findBy~メソッドは要素取得を待機してくれる、ということだけ理解していたので、findBy~メソッド自体の待機時間が1秒だということに気付かず、原因特定に時間がかかりました...!
参考
JISOUのメンバー募集中!
プログラミングコーチングJISOUでは、新たなメンバーを募集しています。
日本一のアウトプットコミュニティでキャリアアップしませんか?
興味のある方は、ぜひホームページをのぞいてみてください!