はじめに
next.js×TypeScriptで作成したアプリに対して、Jestでテスト実行したところ、下記のようなエラーが出ました。
和訳すると「ラベルのテキスト(タイトル)は見つかったけど、そのラベルに関連付けられたフォームコントロールが見つからない。」とのことです。
TestingLibraryElementError: Found a label with the text of: タイトル, however no form control was found associated to that label. Make sure you're using the "for" attribute or "aria-labelledby" attribute correctly.
テスト時にはgetByLabelText
を使ってタイトルを取得しようとしています。
page.test.tsx
it("フォームが正しく表示されること", () => {
render(<Page />);
expect(screen.queryByText("投稿作成")).toBeInTheDocument();
expect(screen.getByLabelText("タイトル")).toBeInTheDocument();
expect(screen.getByLabelText("内容")).toBeInTheDocument();
expect(screen.getByLabelText("ユーザー名")).toBeInTheDocument();
expect(screen.getByRole("button", { name: "投稿" })).toBeInTheDocument();
});
テスト対象コードはこちら
page.tsx
<div>
<label className="block mb-2 text-indigo-500">
タイトル
</label>
<input
className="w-full p-2 mb-2 text-indigo-700 border-b-2 border-indigo-500 outline-none focus:bg-gray-300"
type="text"
{...register("title")}
/>
{errors.title && (
<p className="text-red-500">{errors.title.message}</p>
)}{" "}
</div>
原因
私がgetByLabelText
の用法をいまいち理解しないまま使っていました。
公式ドキュメントにある通り、getByLabelText
は下記のように設定されたDOM構造の入力ノードを検索します。
つまり、<input>
要素にid属性を追加し、<label>
のfor
属性と一致させる必要があります。
tsx
// `username`を検索する例
<label for="username-input">Username</label>
<input id="username-input" />
上記のほかにも複数例載っています。
解決方法
上記の修正を適用して、<input>
要素にid="title"
を追加し、<label>
要素にもidと同じ値 htmlFor="title"
を付与しました。
これで無事にテストが通りました。
page.tsx
<div>
<label htmlFor="title" className="block mb-2 text-indigo-500">
タイトル
</label>
<input
id="title"
className="w-full p-2 mb-2 text-indigo-700 border-b-2 border-indigo-500 outline-none focus:bg-gray-300"
type="text"
{...register("title")}
/>
{errors.title && (
<p className="text-red-500">{errors.title.message}</p>
)}{" "}
</div>
おわりに
公式ドキュメントでは、使用するクエリの優先順位なんかも定義されています。
自分の知っているクエリだけを使いまわすんじゃなくて、しっかりと各クエリの用法を理解しながら適切な選択をしていきたいです。