【ITニュース解説】🎭How to test Next.js SSR API (Playwright + MSW)🎭
2025年09月07日に「Dev.to」が公開したITニュース「🎭How to test Next.js SSR API (Playwright + MSW)🎭」について初心者にもわかりやすいように丁寧に解説しています。
ITニュース概要
Next.jsのSSR APIテストをPlaywrightとMSWで自動化する手法を解説。APIモックの動的変更やテストの並列実行で発生する問題に対し、MSWの`once`オプションやPlaywrightの設定変更で解決する。具体的な手順とコード例を示す。
ITニュース解説
ソフトウェア開発において、作成したアプリケーションが正しく機能するかを確認する「テスト」は非常に重要です。特に、ユーザーが実際に操作する画面を通じてアプリケーション全体を検証する「エンドツーエンド(E2E)テスト」は、システムの信頼性を高めるために欠かせない工程と言えます。この解説では、Next.jsという人気のウェブフレームワークで構築された、サーバーサイドレンダリング(SSR)を用いたアプリケーションのAPIを、PlaywrightとMock Service Worker(MSW)という二つのツールを組み合わせて効率的にテストする方法を掘り下げます。
Next.jsは、ウェブサイトのページをブラウザに送る前に、サーバー側でHTMLを生成する「サーバーサイドレンダリング(SSR)」という機能を提供しています。これにより、ページの表示が速くなったり、検索エンジンでの見つけやすさが向上したりするメリットがあります。しかし、このSSRの動作、特にサーバー側で行われるAPI呼び出しを含む部分をテストする際には、通常のクライアントサイドレンダリング(CSR)のテストとは異なる、いくつかの特別な考慮が必要です。
Playwrightは、Microsoftが開発したオープンソースのE2Eテストフレームワークです。これは、Chrome、Firefox、WebKitといった様々なウェブブラウザを自動的に操作し、クリックや文字入力、画面の表示内容の確認など、ユーザーが行う一連の操作をシミュレートしてテストを実行します。Playwrightはデフォルトでブラウザ上の動作(CSR)のテストに優れていますが、今回のようなNext.jsのSSRで行われるサーバー側のAPI通信をテストするには、追加の工夫が必要となります。
テストを行う際、アプリケーションが常に実際のAPIサーバーに接続してデータを取得するのは、準備の手間やテスト環境の安定性、実行速度の面で課題が生じがちです。ここで「Mock Service Worker(MSW)」が重要な役割を果たします。MSWは、アプリケーションが行うネットワークリクエスト(API呼び出しなど)を傍受(インターセプト)し、実際のAPIサーバーからの応答の代わりに、事前に用意しておいた「モックデータ」(偽のデータ)を返すライブラリです。これにより、バックエンドのAPIサーバーがまだ開発途中であったり、不安定な状態であっても、フロントエンド側のテストを独立して、かつ安定して実行することが可能になります。MSWは、ブラウザ環境でもNode.js(サーバー側)環境でも動作するため、Next.jsのSSRテストには特に適しています。
このPlaywrightとMSWを組み合わせてテスト環境を構築する過程で、いくつかの具体的な課題に直面し、それを解決していきました。
まず最初の課題は、Playwrightがデフォルトでブラウザテスト(CSR)に特化しているため、Next.jsのSSRで行われるサーバー側のAPI呼び出しをテストするための設定が容易ではなかった点です。この問題に対する解決策として、Next.jsアプリケーションを通常の3000番ポートではなく、例えば3001番ポートのような別のポートで実行するように設定しました。そして、MSWを使ってそのポートへのAPIリクエストを傍受し、事前に準備したモックデータを返すようにすることで、Playwrightは実際のAPIサーバーではなくMSWが提供するモックデータを受け取って画面を表示できるようになります。
二つ目の課題は、APIのテストでは、正常にデータが取得できるケースだけでなく、データが見つからない場合、ユーザーの入力に誤りがある場合、サーバー側でエラーが発生した場合など、様々なレスポンスパターンを検証する必要があることです。しかし、MSWは基本的な設定では、一度に一つのAPIエンドポイントに対して一つのモックレスポンスしか定義できません。MSWの推奨するベストプラクティスには、APIのパスにパラメータを含めてレスポンスを切り替える方法が紹介されていますが、これは主にクライアントサイド(ブラウザ)で使うことを想定しており、Next.jsのSSR環境には直接適用しにくいという問題がありました。
この課題を解決するために、MSWが提供する server.use() というメソッドを活用しました。このメソッドを使うと、テストの実行中に、特定のAPIエンドポイントに対するモックの振る舞いを一時的に上書きできます。さらに、Playwrightのテストコードから、どのモックデータを返してほしいかをMSWに伝えるための専用の「切り替えAPI」を独自に構築しました。Playwrightは、この切り替えAPIに対してリクエストを送信することで、MSWが返すモックデータを動的に変更し、様々なテストシナリオに対応できるようにしました。
しかし、server.use() を使用すると、一度設定したモックの上書きが永続的に続いてしまうという新たな課題が発生しました。これでは、あるテストケースで設定したモックデータが、その後に実行される別のテストケースに影響を与えてしまい、各テストケースの独立性が保証されません。テストごとに server.resetHandlers() というメソッドを呼び出してモックをリセットする方法も考えられましたが、これではテストコードが複雑になり、読みにくくなってしまいます。
この「永続的な上書き」の問題をスマートに解決したのが、server.use() メソッドに { once: true } というオプションを指定することでした。このオプションを使用すると、設定したモックの上書きは一度のリクエストに対してのみ適用され、次のリクエストからは自動的にデフォルトのモックハンドラに戻るようになります。これにより、明示的なリセット処理を記述することなく、各テストケースが完全に独立した状態で実行され、互いに影響を与えないクリーンなテスト環境を簡単に実現できました。
最後の課題は、Playwrightがデフォルトで複数のテストケースを同時に並行して実行する「並列実行」機能を備えていることです。これはテストの実行時間を大幅に短縮できる強力な機能ですが、MSWがモックの状態をグローバルに管理しているため、並列実行すると複数のテストが同時にMSWの状態を変更しようとし、状態が競合してテストが予期せぬ結果になったり、失敗したりするという問題が発生しました。
この問題に対しては、Playwrightの設定を変更し、テストを一つずつ順番に実行する「シーケンシャル実行」に切り替えることで解決しました。具体的には、Playwrightの設定ファイルで fullyParallel: false を設定して並列実行を無効にし、workers: 1 を設定して同時に実行するテストの数を1つに制限しました。これによりテストの実行速度は若干遅くなりますが、MSWの状態競合による問題を確実に回避し、安定して正確なテスト結果が得られるようになりました。将来的には、より効率的な並列実行の方法が見つかれば、さらにテスト効率を向上させることができるでしょう。
これらの課題と解決策を経て、Next.js SSR APIをPlaywrightとMSWでテストする具体的な手順が確立されました。
まず、Next.jsプロジェクトを新規に作成し、ポケモンデータを表示するシンプルなサーバーコンポーネントを実装します。このコンポーネントは、http://localhost:3001/api/v2/pokemon/charizardというAPIエンドポイントからデータを取得する設計です。
次に、E2EテストフレームワークであるPlaywright、APIモックライブラリであるMSW、そしてTypeScriptファイルを直接実行するためのtsxというツールをプロジェクトにインストールします。
Playwrightのテストコードはtests/example.spec.tsに記述します。ここではsetMockPokemonというヘルパー関数が定義されており、この関数がhttp://localhost:3001/api/switch-pokemonという独自の切り替えAPIに対してPOSTリクエストを送信することで、MSWが返すモックデータを動的に(例えばピカチュウ、イーブイ、あるいは500エラーを返すように)切り替えます。その後、テスト対象のページにアクセスし、画面に表示されるポケモン名やID、またはエラーメッセージが期待通りであることをPlaywrightで検証します。
MSWの設定は、mocks/handlers.tsとmocks/server.tsという二つのファイルに分けて管理します。handlers.tsでは、モックとして返す具体的なポケモンデータ(チャリザード、ピカチュウ、イーブイなど)や、500エラーなどのレスポンス情報、そしてデフォルトでチャリザードのデータを返すハンドラを定義します。server.tsではMSWサーバーのインスタンスを作成し、setMockPokemon関数を通じて、server.use()と{ once: true }を組み合わせることで、特定のポケモンデータを一度だけ返すように一時的にモックを切り替える仕組みを提供します。
さらに、PlaywrightのテストとMSWを連携させるための橋渡し役となる「ブリッジサーバー」をmock-server.tsとして実装します。このサーバーはポート3001で起動し、Playwrightテストから送られるPOST /api/switch-pokemonリクエストを受け取ると、MSWのsetMockPokemon関数を呼び出してモックデータを切り替えます。その他のAPIリクエストはMSWを通して処理され、モックデータがアプリケーションに返されます。
最後に、Playwrightの設定ファイルであるplaywright.config.tsを更新し、テストのベースURLをhttp://localhost:3000に設定します。また、テストが安定して実行されるように、fullyParallel: falseとworkers: 1を設定して、テストが順番に実行されるようにします。
これらの設定とコードが全て準備できたら、まずnpx tsx mock-server.tsコマンドでブリッジサーバーを起動し、次にnpm run devコマンドでNext.jsアプリケーションを起動します。そして、npx playwright testコマンドを実行することで、E2Eテストが開始されます。このテストでは、各テストケースで意図した異なるポケモンデータやエラーメッセージが画面に表示されることを検証でき、実際にチャリザード、ピカチュウ、イーブイ、そして500エラーが表示されたページのスクリーンショットからも、テストが期待通りに機能していることが確認できます。
このように、PlaywrightとMSWという強力なツールを組み合わせ、発生する課題に対して適切な解決策を適用していくことで、Next.jsのSSR APIを含む複雑なアプリケーションの結合テストを効果的に自動化する方法を構築できます。このテスト手法は、アプリケーションの品質を向上させ、開発プロセスをより効率的かつ安定させるために大いに役立つでしょう。