ホームページ >ウェブフロントエンド >jsチュートリアル >フェッチ モックから MSW まで: テストの旅

フェッチ モックから MSW まで: テストの旅

Patricia Arquette
Patricia Arquetteオリジナル
2024-12-03 11:47:09749ブラウズ

From Fetch Mocks to MSW: A Testing Journey

Catalyst: 無邪気な Axios リファクタリング

それは無邪気に始まりました。 「これらのフェッチ呼び出しをリファクタリングして、Axios を使用するだけです。何が問題になる可能性があるでしょうか?」と私は考えました。結局のところ、かなりの部分、具体的には、私が慎重に作成したフェッチ モックがすべて、突然チョコレート ティーポットと同じくらい便利になりました。

Axios 用にすべてのモックを再構築するのではなく、この機会を利用してアプローチを最新化することにしました。モック サービス ワーカー (MSW) を入力します。

古い方法: Jest モックとフェッチ

以前のテストは次のようになっていました:

const mockFetch = vi.fn();
global.fetch = mockFetch;

describe("API functions", () => {
  beforeEach(() => {
    mockFetch.mockReset();
  });

  test("fetchTrips - should fetch trips successfully", async () => {
    const mockTrips = [{ id: 1, name: "Trip to Paris" }];
    mockFetch.mockResolvedValueOnce({
      ok: true,
      json: async () => mockTrips,
    });

    const trips = await fetchTrips(mockSupabase);
    expect(trips).toEqual(mockTrips);
  });
});

うまくいきましたが、まったくエレガントではありませんでした。各テストには手動のモック設定が必要でしたが、モックは脆弱で、実際の API が現実世界でどのように動作するかを実際には表していませんでした。実際の動作ではなく、実装の詳細をテストしていました。

MSW への参入: モックのより良い方法

Mock Service Worker (MSW) は、API モックに対して根本的に異なるアプローチを採用しています。関数呼び出しをモックする代わりに、実際のネットワーク リクエストをネットワーク レベルでインターセプトします。これはいくつかの理由から非常に重要です:

  • ランタイム統合: MSW は実際の HTTP リクエストをインターセプトすることで機能します。つまり、コードは本番環境とまったく同じように実行されます。フェッチや axios をモックする必要はもうありません。実際の API 呼び出しは変更されずに実行されます。
  • API ファーストの設計: 関数モックについて考える代わりに、実際の API を反映するモック API エンドポイントを定義します。これにより、より良い API 設計に向けて推進され、テストを実際のエンドポイントに合わせた状態に保つことができます。
  • リクエスト/レスポンスの忠実度: 単純化されたモック オブジェクトではなく、実際の HTTP の概念 (ステータス コード、ヘッダー、レスポンス本文) を扱うことができます。これは、より現実的なエッジケースを把握できることを意味します。

これらの同じテストが MSW でどのように見えるかは次のとおりです:

// Your API handler definition
http.get(`${BASE_URL}/trips`, () => {
  return HttpResponse.json([
    { id: "1", location: "Trip 1", days: 5, startDate: "2023-06-01" },
    { id: "2", location: "Trip 2", days: 7, startDate: "2023-07-15" },
  ]);
});

// Your test - notice how much cleaner it is
test("fetchTrips - should fetch trips successfully", async () => {
  const trips = await fetchTrips();
  expect(trips).toEqual([
    { id: "1", location: "Trip 1", days: 5, startDate: "2023-06-01" },
    { id: "2", location: "Trip 2", days: 7, startDate: "2023-07-15" },
  ]);
});

テストごとに手動でモックをセットアップする必要はなくなり、MSW ハンドラーがすべて処理します。さらに、これらのハンドラーは多くのテストで再利用できるため、重複が減り、テストがより保守しやすくなります。

セットアップ

MSW のセットアップは驚くほど簡単だったので、すぐに疑問に思いました。テストにおいてこれほど簡単なことはありません...

beforeAll(() => {
  server.listen({ onUnhandledRequest: "bypass" });
});

afterEach(() => {
  server.resetHandlers();
  cleanup();
});

afterAll(() => {
  server.close();
});

次に、実際に私の API に似たハンドラーを作成します。

export const handlers = [
  http.get(`${BASE_URL}/trips`, () => {
    return HttpResponse.json([
      { id: "1", location: "Trip 1", days: 5, startDate: "2023-06-01" },
      { id: "2", location: "Trip 2", days: 7, startDate: "2023-07-15" },
    ]);
  }),
];

エラー処理の旅

エラー処理に対する私の最初の試みは...まあ、楽観的だったとしましょう:

export const errorHandlers = [
  http.get(`${BASE_URL}/trips/999`, () => {
    return new HttpResponse(null, { status: 404 });
  }),
];

問題は?より一般的な /trips/:id ハンドラーが最初にすべてをキャッチしていました。これは、Express アプリで特定のルートの前に包括的なルートを設定するようなものでした。初歩的なミスです。

何度か頭を悩ませてテストに失敗した後、より良いアプローチはルート自体内でエラーを処理することであることに気付きました。

const mockFetch = vi.fn();
global.fetch = mockFetch;

describe("API functions", () => {
  beforeEach(() => {
    mockFetch.mockReset();
  });

  test("fetchTrips - should fetch trips successfully", async () => {
    const mockTrips = [{ id: 1, name: "Trip to Paris" }];
    mockFetch.mockResolvedValueOnce({
      ok: true,
      json: async () => mockTrips,
    });

    const trips = await fetchTrips(mockSupabase);
    expect(trips).toEqual(mockTrips);
  });
});

このパターンが現れました。個別のエラー ハンドラーの代わりに、実際の API と同じように、成功ケースとエラー ケースの両方を同じ場所で処理できます。それは「ああ!」というものでした。テストによって実際に優れた設計に向かう瞬間です。

学んだ教訓

  1. 適切なレベルでモックする: MSW を使用すると、機能レベルではなくネットワーク レベルでモックできるため、テストがより現実的で堅牢になります。
  2. 関数ではなくエンドポイントで考える: 個々の関数呼び出しではなく、API エンドポイントを中心にモックを構造化することで、実際のアプリケーションの動作をよりよく表現できます。
  3. エラーが発生した場所でエラーを処理する: 個別のエラー ハンドラーの代わりに、実際の API と同様に、エンドポイント ハンドラー自体内でエラーを処理します。

最終結果

最終的なセットアップは、より保守しやすく、より現実的で、実際の問題を把握するのに役立ちます。
の時代は終わりました。

// Your API handler definition
http.get(`${BASE_URL}/trips`, () => {
  return HttpResponse.json([
    { id: "1", location: "Trip 1", days: 5, startDate: "2023-06-01" },
    { id: "2", location: "Trip 2", days: 7, startDate: "2023-07-15" },
  ]);
});

// Your test - notice how much cleaner it is
test("fetchTrips - should fetch trips successfully", async () => {
  const trips = await fetchTrips();
  expect(trips).toEqual([
    { id: "1", location: "Trip 1", days: 5, startDate: "2023-06-01" },
    { id: "2", location: "Trip 2", days: 7, startDate: "2023-07-15" },
  ]);
});

代わりに、次のような適切な API モックがあります。

  • 成功ケースとエラーケースの両方を処理します
  • 現実的な応答構造を使用する
  • 複数のテストで再利用可能
  • 統合の問題を実際にキャッチする

次は何ですか?

楽しみにしていること:

  • ネットワークエラーをより現実的にシミュレートする
  • エンドツーエンドのテストに MSW のブラウザ統合を使用する
  • テストの読み込み状態に応答遅延を追加する

時には、変化を強いられることで最良の改善が得られることもあります。単純な Axios リファクタリングとして始まったものは、最終的にははるかに優れたテスト アーキテクチャにつながりました。それがリファクタリングの本質ではないでしょうか?


この記事はもともと私のブログに公開されたものです。フルスタック開発、テスト、API 設計に関する詳細なコンテンツについては、こちらをフォローしてください。

以上がフェッチ モックから MSW まで: テストの旅の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。