【ITニュース解説】Cause and solution for the "Unable to find an element with the text" error in React Hook Form + Chakra UI
2025年09月29日に「Dev.to」が公開したITニュース「Cause and solution for the "Unable to find an element with the text" error in React Hook Form + Chakra UI」について初心者にもわかりやすく解説しています。
ITニュース概要
React Hook FormとChakra UIフォームで、UIに表示されるエラーメッセージがJestテストで検出できない問題が発生した。バリデーションエラーがDOMに即時反映されないのが原因だ。解決策は、非同期で要素検出を待つ`findByText`を使用すること。UIとテストの動作乖離時は、非同期処理を疑うのが有効だ。
ITニュース解説
Webアプリケーション開発において、ユーザーからの入力を受け付けるフォームは非常に重要な要素だ。例えば、ログイン画面のIDやパスワード、商品の購入情報、アンケートの回答など、さまざまな場面でフォームが利用される。これらのフォームが正しく機能し、特にユーザーが誤った情報を入力した場合に適切なフィードバックを返すことは、使いやすいアプリケーションを作る上で不可欠である。そして、その機能が意図通りに動くことを確認するために「テスト」が行われる。
今回の記事では、React Hook FormとChakra UIという二つの人気のあるツールを使ってフォームを開発する際に直面した、特定のテストエラーとその解決策について解説する。React Hook Formは、Reactアプリケーションでフォームの状態管理やバリデーション(入力値検証)を効率的に行うためのライブラリだ。一方、Chakra UIは、UIコンポーネントを素早く構築するためのデザインシステムであり、見た目の良いフォームを簡単に作成できる。これらを組み合わせることで、開発者は強力で見た目にも美しいフォームを効率的に実装できる。
問題は、開発者が必須入力項目のあるフォームを作成し、そのバリデーション機能、つまり「IDは必須です」といったエラーメッセージが正しく表示されることを確認しようとしたときに発生した。アプリケーションをブラウザで実際に動かすと、入力がされていない場合にエラーメッセージはきちんと画面に表示された。これはユーザー体験としては正しい挙動だ。しかし、この挙動を自動的に検証する「Jest」というテストフレームワークを使ったテストコードを実行すると、「Unable to find an element with the text: ID is required」というエラーメッセージが表示され、テストが失敗してしまった。
なぜこのような矛盾が生じたのだろうか。アプリケーション上ではエラーメッセージが見えているのに、テストでは見つけられないというのは奇妙に思える。この原因は、Webページの構造を司る「DOM(Document Object Model)」と呼ばれるものと、JavaScriptの実行のタイミング、特に「非同期処理」という概念に関わっている。Webページは、HTMLの要素がツリー構造になったDOMとして表現される。JavaScriptがこれらの要素を追加したり変更したりすると、DOMが更新され、それが画面に反映されることで、私たちはWebページの変化を目にする。
今回のケースでは、React Hook Formがフォームのバリデーションを行い、エラーを検知した後、そのエラーメッセージをDOMに追加する処理が、テストコードがエラーメッセージを探しに行くタイミングよりも「後」に行われていたのだ。つまり、エラーメッセージが画面に表示されるまでの間にわずかな時間差(非同期性)があったわけである。
テストコードでは、「Testing Library」という、よりユーザーの視点に近いテストを書くためのライブラリがよく使われる。このライブラリには、DOMから特定のテキストを持つ要素を探すための screen.getByText という関数がある。getByText は、実行された瞬間に現在のDOMの状態を調べて要素を探す。しかし、エラーメッセージがまだDOMに追加されていない状態、つまり「ID is required」というテキストを持つ要素が存在しない状態で getByText が実行されてしまっていたため、要素を見つけることができずにテストが失敗したのである。
この問題を解決するために導入されたのが、Testing Libraryの別の関数である screen.findByText だ。findByText は getByText とは異なり、非同期処理をサポートしている。これはどういうことかと言うと、findByText は要素が見つかるまで「待ってくれる」という特徴がある。指定したテキストを持つ要素がDOMに現れるまで、一定時間(デフォルトでは数秒)ポーリング(繰り返し確認)を行い続けるのだ。
具体的なコードで見てみよう。失敗したテストコードは以下のようだった。
expect(screen.getByText("ID is required")).toBeInTheDocument();
これは「DOMに"ID is required"というテキストを持つ要素があることを期待する」という意味だが、getByText が同期的に実行されるため、タイミングによってはまだ要素が存在せず、テストが失敗した。
これを解決した成功例のコードは以下の通りだ。
expect(await screen.findByText("ID is required")).toBeInTheDocument();
ここで注目すべきは await キーワードと findByText だ。await は、JavaScriptの非同期処理において、その処理が完了するまで次の処理の実行を一時停止させる役割を持つ。つまり、「screen.findByText が"ID is required"というテキストを持つ要素を見つけるまで待機し、見つかったらその結果を使ってテストを続行する」という指示になる。これにより、React Hook FormがエラーメッセージをDOMに反映するまでのわずかな遅延を考慮に入れ、テストコードが正しく要素を捕捉できるようになった。結果として、アプリケーションのUI上とテスト上の挙動の不一致が解消され、テストが期待通りに成功したのである。
この経験から学べる重要な教訓は、Webアプリケーション開発、特にReactのようなコンポーネントベースのフレームワークでは、UIの更新が必ずしも即座にDOMに反映されるわけではない、という点だ。このような「非同期性」はWeb開発において頻繁に登場する概念であり、ユーザーの操作やデータの取得、アニメーションの表示など、さまざまな場面で意識する必要がある。テストを書く際も、もしUIとテストの挙動が異なる問題に直面したら、まず非同期処理が絡んでいないか、要素の表示タイミングに遅延がないかという点を疑ってみるのが良いアプローチとなるだろう。非同期処理を適切に扱えるようになることは、システムエンジニアを目指す上で非常に重要なスキルの一つである。