EnzymeからReact Testing Libraryへテストを移行する方法
ReactのテストスイートがまだEnzymeに依存している場合、数年前に公式アップデートが停止したライブラリに対してコードをメンテナンスしていることになります。EnzymeにはReact 18や19用の公式アダプターが存在せず、非公式の回避策は信頼性に欠けます。進むべき道は明確です:React Testing Library (RTL)への移行です。
本記事では、EnzymeからReact Testing Libraryへの移行を成功させるために必要な概念的な転換、一般的なリファクタリングパターン、そして2025年にこの作業に取り組むチームのための実践的なガイダンスを取り上げます。
重要なポイント
- EnzymeにはReact 18および19用の公式アダプターがないため、モダンなReactプロジェクトではReact Testing Libraryへの移行が不可欠です。
- RTLは実装の詳細ではなく、ユーザーに見える動作のテストに焦点を当て、より安定した意味のあるテストを生成します。
- Enzymeの
shallow()とmount()をRTLのrender()に置き換え、find()の代わりにgetByRole()のようなアクセシブルなクエリを使用します。 - 両方のライブラリを並行して実行し、シンプルなコンポーネントから始めてバッチ単位でテストを変換することで、段階的に移行します。
なぜEnzymeはもはや実用的でないのか
Enzymeは長年にわたってReactテストツールの主流でした。しかし、Reactの内部実装との密接な結合が負債となりました。React 17がリリースされた際、コミュニティアダプターがそのギャップを埋めました。React 18はそのパターンを完全に破壊しました—公式アダプターは存在せず、Enzymeプロジェクトが事実上メンテナンスされていないため、今後も発表される予定はありません。
互換性の問題に加えて、Enzymeは実装の詳細をテストすることを推奨していました:内部状態のチェック、インスタンスメソッドの呼び出し、子コンポーネントから分離するためのshallowレンダリングの使用などです。これらのパターンは、動作が同じままでもリファクタリング時に壊れやすい脆弱なテストを生み出します。
React Testing Libraryは正反対のアプローチを取ります。コンポーネントを完全にレンダリングし、ユーザーが操作するのと同じ方法でDOMをクエリします—役割、ラベル、テキストコンテンツによって。これはモダンなReactテストのベストプラクティスと一致し、実装の変更を通じて安定したテストを生成します。
核となる哲学の転換
この移行における最大の課題は構文ではありません。マインドセットです。
Enzymeのテストは多くの場合、次のようになります:
wrapper.instance()を介してコンポーネントインスタンスにアクセスsetState()またはsetProps()を直接呼び出す- 内部状態の値をアサート
shallow()を使用して子コンポーネントのレンダリングをスキップ
これらのいずれにも、直接的なRTLの同等物はありません。RTLは、ユーザーが決して見ることのないものをテストするため、意図的にこれらを省略しています。
代わりに、RTLのテストは以下に焦点を当てます:
- DOMに何がレンダリングされるか
- 要素がユーザーの操作にどのように応答するか
- アクセシブルな役割とラベルが存在するかどうか
Enzymeのshallowレンダリングを置き換えるには、完全なコンポーネントツリーをレンダリングし、必要に応じてモジュールレベルで依存関係をモックします。これにはより多くのセットアップが必要ですが、より意味のあるカバレッジを生み出します。
Discover how at OpenReplay.com.
一般的なリファクタリングパターン
shallow()とmount()をrender()に置き換える
RTLのrender()関数は、コンポーネントをDOM環境にマウントします。shallowに相当するものはありません。子コンポーネントが問題を引き起こす場合は、Jestでモックします:
jest.mock('./ChildComponent', () => () => <div data-testid="child-mock" />);
wrapper.find()をアクセシブルなクエリに置き換える
Enzymeのfind('button')は、RTLのscreen.getByRole('button')になります。ユーザーが要素を見つける方法を反映するクエリを優先します:
- インタラクティブな要素には
getByRole() - フォーム入力には
getByLabelText() - 表示されるコンテンツには
getByText()
instance()とstate()のアサーションを削除する
ボタンをクリックすると内部状態が更新されることをテストしている場合は、テストを再構成します:クリック後にユーザーは何を見るか?レンダリングされた出力に対してアサートします。
findByとwaitForで非同期動作を処理する
Enzymeは手動のwrapper.update()呼び出しを必要としました。RTLは更新を自動的に処理します。非同期アサーションにはfindByRole()またはwaitFor()を使用します。
実世界での移行は実現可能
大規模なチームがこの移行を成功裏に完了しています。Slack EngineeringはASTベースのcodemodとLLM支援の変換を組み合わせて、15,000以上のテストを変換しました。The New York Timesのエンジニアリングチームは、Enzymeの移行をReact 18アップグレードの最大の部分と説明しました。
一般的なアプローチ:段階的に移行します。両方のライブラリを並行して実行します。バッチ単位でテストを変換し、シンプルなコンポーネントから始めます。構文の変更が機械的な場合は自動化を使用しますが、実装の詳細に大きく依存していたテストには手作業が必要です。
React 19とコンポーネントテストの未来
React 19はコンポーネントテスト用のreact-test-rendererを非推奨とし、RTLを標準としてさらに統合します。Reactのアップグレードを計画している場合、EnzymeからReact Testing Libraryへの移行を先に完了することで、大きな障害を取り除くことができます。
2025年のモダンなReactテストとは、リファクタリングに耐え、アクセシビリティを検証し、実際のユーザー動作を反映するテストを書くことを意味します。RTLはこれら3つすべてを実現します。
まとめ
Enzymeからの移行には、単純な検索と置換以上のものが必要です。内部実装よりもユーザーに見える動作を優先する、異なるテスト哲学を採用することになります。この努力は、より安定し、より意味があり、現在および将来のReactバージョンと互換性のあるテストという形で報われます。
小さなバッチから始めましょう。ユーザーが見るものを中心にアサーションを再構成します。shallowレンダリングを手放しましょう。テストスイートはそれによって改善されます。
よくある質問
はい。両方のライブラリは同じプロジェクト内で共存できます。これにより、既存のテストスイートを中断することなく、段階的にテストを移行できます。EnzymeとともにRTLをインストールし、バッチ単位でテストを変換し、移行が完了したらEnzymeを削除します。
RTLは内部状態を直接テストすることを推奨していません。代わりに、観察可能な結果をテストします。ボタンをクリックすると状態が変わる場合、クリック後にユーザーが見るもの(更新されたテキスト、表示される新しい要素、変更された属性など)を検証します。このアプローチはより回復力のあるテストを生み出します。
Jestを使用してモジュールレベルで問題のある子コンポーネントをモックします。これにより、親コンポーネントを完全にレンダリングしながら、子の実装からテストを分離できます。jest.mockを使用して、子をシンプルなプレースホルダー要素に置き換えます。
はい。RTLはReact 19の推奨テストライブラリであり続けます。React 19はコンポーネントテスト用のreact-test-rendererを非推奨とし、RTLを標準的な選択肢としています。React 19にアップグレードする前に移行を完了することで、重要な互換性の障害を取り除くことができます。
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.