テストは困難なビジネスです。フルスタックアプリのテストはさらに困難です。あなたはフロントエンド、バックエンド、データベース、ネットワーク、その他に対処する必要があります。 まず、もちろん、あなたは単独でコンポーネント、機能、モジュールをテストします。 しかし、そこには混沌の要因があります:物事が間違ったときに何が起こりますか?ネットワークが遅い場合や信頼性が低い場合に何が起こりますか?バックエンドが低下した場合に何が起こりますか? 幸せな道で完璧に動作するアプリケーションは、予期せぬことが起こるときにも簡単に壊れます。 Chaos-driven testing は、あなたのテストに故意に失敗を導入することによって、この不確実性を抱くアプローチです この記事では、故意に物事を壊す統合テストを使用して Next.js アプリケーションで chaos-driven testing を実装する方法について説明します。 【アプリ】 まず、テストするためのアプリが必要です。記事を集中させるために、私は最低限のフルスタックのNext.jsアプリを作成しましたので、あなたは必要ありません。 アプリは、レシピのリストを閲覧し、レシピの詳細を表示し、それらのようにシンプルなレシピアプリです. It uses Tailwind CSS for styling and TypeScript for type security. あなたがレシピを好むとき、Like countはフロントエンド上で楽観的に更新し、バックエンドはリクエストを処理します。バックエンドの呼び出しが失敗した場合、Like countは以前の状態に戻ります。 成功した場合、それは新しいLike countを返しますが、フロントエンドは、レシピが他のユーザーに好かれていた場合にジャンプする数字を回避するためにそれを再更新しません。 コードはこちらで入手可能 . GitHub レポをチェックし、依存性をインストールし、以下でローカルで実行できます。 git clone cd article-chaos-fetch npm install npm run dev オープン あなたのブラウザで http://localhost:3000 あなたはこんなものを見るでしょう: バックエンドには3つのAPIルートがあります。 GET /api/posts — list all recipes レシピ GET /api/posts/[id] — get recipe details POST /api/posts/[id]/like — increment likes - returns the new like count 好みはメモリ内に保存され、サーバーの再起動時にリセットされることに注意してください. This is only for demonstration purposes; in a real app, you would use a database. 同様のボタンが実装されている。 Reactの使い方 そして それは楽観的な更新、エラー処理、および逆転論理を処理します. おそらくあなたや私は実際のアプリでそれを実装する方法ではありませんが、このデモのためにそうします。 src/components/LikeButton.tsx useState useEffect Mock Service Worker (MSW) によるユニットテスト コンポーネントを単位でテストしたい場合は、それを行うための複数の方法がありますが、最も賢い方法の1つは使用することです。 この方法で、実際のバックエンドに頼ることなく、コンポーネントを孤立的にテストできます。 Mock Service Worker(MSW)について IN THE Branch, We Set Up テストランナーとして、そして コンポーネントをレンダリングし、ユーザーのインタラクションをシミュレートするために、また、ネットワークリクエストをキャプチャし、マックの応答を返すためにMSWを設定しました。 main 速い React テスト 図書館 ユニットテストを含む コンポーネントは、以下のシナリオをテストします。 src/components/LikeButton.test.tsx LikeButton 同様のボタンは、リクエスト中に無効にし、成功時にバックエンド値に更新します。 同様のボタンはリクエスト中に無効になり、バックエンドエラーに戻ります。 あなたはテストを実行することができます: npm run test テストコードを見てみると、MSW を使用してバックエンドの回答を嘲笑する方法がわかります. たとえば、最初のテストでは、42 の新しい like count で成功した回答を返すために、マックを上回ります: // test("like button disables during request and updates to backend value" ... server.use( http.post("/api/posts/:id/like", async () => { await new Promise((resolve) => setTimeout(resolve, 100)); return { likes: 42 }; }) ); ... 2 番目のテストでは、誤答を返すためにマックを上回ります。 test("like button disables during request and rolls back on backend error" ... server.use( http.post("/api/posts/:id/like", async () => { await new Promise((resolve) => setTimeout(resolve, 100)); return { status: 500 }; }) ); ... MSW が行うことは、ネットワーク リクエストを遮断することです。 コンポーネントと、テストで定義した偽りの反応を返します. この方法で、コンポーネントが実際のバックエンドに頼ることなく、異なるバックエンド条件下でどのように振る舞うかをテストできます. However, no real backend is involved, so we cannot test the full integration between frontend and backend! LikeButton 統合テスト では、フロントエンドとバックエンドの完全な統合をテストするにはどうすればよいでしょうか? 技術的には、MSWを変更して実際のバックエンドにリクエストを転送することができますが、それは少しハッキングで、MSWが本当に何のために設計されているかではありません。 または end-to-end テストを実行し、standalone proxy を使用するには、 あるいは、より単純なものとして、 ネットワーク条件をシミュレートするために - しかし、これはこのシンプルなアプリにとって少し過剰です。 プレイヤー キプロス 毒性プロキシ カオスプロキシ ここはどこ It is a lightweight library that wraps the native. それは、ネイティブを包み込む軽い図書館です。 API を使用すると、ネットワークリクエストに混乱を導入できます. You can simulate latency, errors, rate limiting, throttling, and even random failures with just a few lines of code. カオスフェッチ fetch 使ってみよう フロントエンドとバックエンドの完全な統合をテストしたい場合は、実際のブラウザ環境でテストを実行する必要があります。 これは、Node.js でブラウザのような環境をシミュレートする環境です。 chaos-fetch jsdom To use `chaos-fetch`, we first need to install it: ```bash npm install @fetchkit/chaos-fetch --save-dev 私たちができる最初のことは、説明の目的のために、MSWと交換することです。 私たちのユニットテストでは、実際に図書館が目指しているものではありませんが、機能します。 , we replace the MSW setup with : chaos-fetch LikeButton.test,tsx chaos-fetch // src/components/LikeButton.test.tsx import { createClient, replaceGlobalFetch, restoreGlobalFetch, } from "@fetchkit/chaos-fetch"; ... describe("LikeButton", () => { afterEach(() => { restoreGlobalFetch(); }); test("like button disables during request and updates to backend value", async () => { // Mock fetch to return success const client = createClient( { global: [ { latency: { ms: 300 } }, ], routes: { "POST /api/posts/:id/like": [ { latency: { ms: 300 } }, { mock: { body: '{ "likes": 43 }' } }, ], }, }, window.fetch ); // Replace global fetch with mock client replaceGlobalFetch(client); // From here on, the test code remains the same ... ご覧のとおり、私たちはA クライアントは、キャッチする代わりに、いくつかのマックデータを返し、グローバルデータを置き換える。 役割を果たす. in the hook, we restore the original オリジナル 機能:テストコードの残りは同じです。 chaos-fetch fetch afterEach fetch 注:実際のネットワークリクエストをシミュレートするために、いくつかの遅延も追加しました。 コンポーネントはリクエスト中にボタンを無効にし、我々はその行動をテストしたい。それがないと、リクエストがあまりにも速く完了するので、テストは失敗するだろう(そしてこれは時間ベースのテストの脆弱で信頼できない世界に私たちを導きますが、それは別の記事の話題です)。 LikeButton 第2のテストのコードは同じですが、ミックを変更してエラーを返します。 test("like button disables during request and rolls back on backend error", async () => { // Mock fetch to return success const client = createClient( { global: [], routes: { "POST /api/posts/:id/like": [ { latency: { ms: 300 } }, { mock: { status: 500, body: '{ "error": "Internal Server Error" }' } }, ], }, }, window.fetch ); // Replace global fetch with mock client replaceGlobalFetch(client); ... The code is on the repoの branch. You can check it out with . tests-with-chaos-fetch git checkout tests-with-chaos-fetch 今、私たちは、テストを実行することができます: npm run test 現在、MSWはユニットテストにまだ適しているため、その目的のために設計されています。 そのためにも。 chaos-fetch Chaos-Driven 統合テスト ユニットテストを超え、フロントエンドとバックエンドの完全な統合をテストしたい場合は、 ネットワークリクエストに混乱をもたらすため、アプリケーションが不利な条件下でどのように振る舞うかをテストできます。 chaos-fetch まず、統合テストのために、我々は小さなリファクターをしなければならない:我々はテストで直接アシンクサーバーコンポーネントを再現することはできない。 を含むコンポーネント レシピの詳細はこちらからお試しいただけます。 私たちの統合テストで、コードは は in . PostPage LikeButton LikeButton PostView src/components/PostView.tsx 次に、我々はテストを実行する前にバックエンドが実行されていることを確認する必要があります。我々のケースでは、それがバックエンドが別々のサービスである場合も同様に統合テストを設定することができます。 私たちが行った同じテストは、 に書き換えられています。 フロントエンドとバックエンドの完全な統合をテストするためにコードは類似していますが、バックエンドの回答を嘲笑する代わりに、私たちはリクエストを実際のバックエンドに送信します。 要求にエラーを入力する。 LikeButton.test.tsx PostView.integration.test.tsx chaos-fetch 重要な点は、設定しなければならないことです。 URL オブジェクトは、 相対 URL を正しく解決できます. この設定では、ネイティブ キャッチで JSDOM は、相対 URL でさえ機能しません! JSDOMのパッチ 働かせるために。 globalThis.location chaos-fetch chaos-fetch location まず、我々は設定 : globalThis.location globalThis.location = new URL("http://localhost:3000/posts/1"); わたしたちは創造する。 ネイティブキャッチを上回るテストのクライアント、そして私たちは遅延、エラー、その他を注入することができます。 chaos-fetch 最初のテストでは、変更する必要もありません。 ; we only override it so it works with relative URLs: fetch test("integration: like button disables during request and re-enables after fetch (real backend)", async () => { replaceGlobalFetch(createClient({})); render(<PostView postId={1} />); // Wait for post to load (like count should be present) const likeCountText = await screen.findByText(/\d+\s*likes/); const initialCount = Number(likeCountText.textContent.match(/(\d+)/)?.[1] ?? 0); const button = await screen.findByRole("button", { name: /like/i }); const user = userEvent.setup(); await user.click(button); // Button should be disabled during request expect(button).toBeDisabled(); // Wait for fetch to complete and UI to update await waitFor(() => expect(button).not.toBeDisabled()); // Check updated like count await waitFor(() => { const updatedLikeCountText = screen.getByText(/\d+\s*likes/); const updatedCount = Number(updatedLikeCountText.textContent.match(/(\d+)/)?.[1] ?? 0); expect(updatedCount).toBe(initialCount + 1); }); restoreGlobalFetch(); }); 第二のテストでは、同じリクエストのバックエンドエラーをシミュレートするクライアントを作成します。 test("integration: like button disables during request and rolls back on backend error (fail middleware)", async () => { // Configure chaos-fetch to fail the like endpoint replaceGlobalFetch(createClient({ routes: { "POST /api/posts/:id/like": [ { latency: { ms: 300 } }, { fail: { status: 500, body: '{ "error": "fail middleware" }' } }, ], }, })); render(<PostView postId={1} />); // Wait for post to load (like count should be present) const likeCountText = await screen.findByText(/\d+\s*likes/); const initialCount = Number(likeCountText.textContent.match(/(\d+)/)?.[1] ?? 0); const button = await screen.findByRole("button", { name: /like/i }); const user = userEvent.setup(); await user.click(button); // Button should be disabled during request expect(button).toBeDisabled(); // Wait for fetch to complete and UI to update await waitFor(() => expect(button).not.toBeDisabled()); // Check that like count rolls back to original value await waitFor(() => { const rolledBackLikeCountText = screen.getByText(/\d+\s*likes/); const rolledBackCount = Number(rolledBackLikeCountText.textContent.match(/(\d+)/)?.[1] ?? 0); expect(rolledBackCount).toBe(initialCount); }); restoreGlobalFetch(); }); 現在、技術的には、第2のテストでは、バックエンドを全く呼びません。 リクエストをキャプチャし、エラーを返しますが、成功したリクエストと失敗したリクエストを同様に処理するための統一されたインターフェイスを提供します。 chaos-fetch このアプローチが本当に輝くのは、より複雑なネットワーク条件をシミュレートしたい場合です。 middleware. Or you can try what happens if your backend is rate-limiting you with the ミドルウェア throttle rateLimit テストするのに困難なもう一つは、リクエストが飛行中である間に遅れたロードスピナーが表示されているかどうかです。 middleware は、遅いネットワークをシミュレートし、ロード状態が正しく表示されていることをテストします。 latency 特定主義を気にしない場合は、アプリが予測不能な条件下でどのように振る舞うかを見るために、いくつかのランダムな失敗を追加することもできます。 結論 混沌のテストはしばしば大規模な分散システムと関連するが、小規模なアプリケーションでも同様に重要である。この記事では、意図的に物事を破壊する統合テストを使用してフルスタックアプリケーションの軽量の混沌駆動テストを調査しました。 当社のネットワーク要求に混乱を招き、当社のアプリケーションが不利な条件下でどのように行動するかをテストするためです。 chaos-fetch これは、MSWやPlaywrightやCypressのような端末テストフレームワークのより良い(あるいはより悪い)代替品ではありません。 混沌を抱きしめ、アプリケーションが失敗条件下でどのように振る舞うかをテストすることで、より強力で強力なアプリケーションを構築することができます。 @fetchkit/chaos-fetch @fetchkit/chaos-proxy