フロントエンド開発に役立つ！MSWでのAPIモック導入方法とそのメリット

## はじめに

[スタンバイ Advent Calendar 2024](https://qiita.com/advent-calendar/2024/stanby)の12日目を担当するUserグループの藤澤です。

https://qiita.com/advent-calendar/2024/stanby

普段はフロントエンド開発を行っています！
当記事では、フロントエンド開発にAPIモックを導入した際のメリットや、具体的な導入方法について紹介します。

## APIモックとは

APIモックとは、仮のAPIレスポンスを返す仕組みです。
これにより、フロントエンド開発者は実際のデータを用いずにAPIの動作を確認したり、アプリケーションの挙動をテストしたりできます。

## 実際に感じたAPIモック導入のメリット

APIモックを導入したことで、開発プロセス全体の効率が大きく向上しました。以下に、実際に導入して感じた具体的なメリットを紹介します。

### 1. バックエンド開発が完了する前にフロントエンドの開発をスタートできる

APIモックを利用することで、バックエンドが完成していなくてもフロントエンドの開発を始めることができました。これにより、開発チームが並行して作業を進められるため、プロジェクト全体の開発スピードが向上しました。

### 2. コードレビュー時にバックエンドのAPIを取り込まなくても動作確認ができる

APIモックを使って作成した仮のAPIレスポンスを利用すれば、レビュー担当者は実際にバックエンドのAPIを呼び出して動作を確認する必要がなく、モックを操作することでフロントエンドの動作確認ができるため、レビューの効率が格段に向上しました。

### 3. テストコードでも作成したモックデータを使いまわせる

テスト時にも、作成したモックデータを再利用できることが大きな利点です。
特定のシナリオをテストするためにモックデータを用意し、そのデータをテストコード内で何度も使い回せるため、テストの維持管理が簡単です。
これにより、ユニットテストや結合テストの際に一貫したデータを使用することができ、テストの信頼性を高めることができました。
また、テストデータを手動で作成する手間も省けるため、テストの効率も良くなりました。

### 4. ニッチなケースを手軽にモックを編集して再現できる

APIモックを使うことで特定のケースやニッチな状況を手軽に再現出来る様になりました！

普段開発しているスタンバイには様々な求人パターンがあり、様々なデータパターンで挙動を確認したい時にAPIをせっせと叩かなくて良くなった恩恵は大きいです！

## MSWとは

https://mswjs.io/

[MSW (Mock Service Worker)](https://mswjs.io/)は、ブラウザとNode.jsのネットワークレベルでモックAPIを実現するためのライブラリです。
これにより、フロントエンドとバックエンドの開発を分離し、様々なシチュエーションを再現できます。

主な特徴には以下があります！

- ネットワークレベルでリクエストをキャッチしてモックレスポンスを返す
- SSRとCSRの両方に対応している
- ローカル開発、Vitestによる結合テスト、Storybookにも結合できる


## プロジェクトへの導入

スタンバイのWebフロントエンドではSSRとCSRの場合で利用するAPIを使い分けています。

今回は以下のAPIを使い分ける想定で説明して行きます！

- ``/api/top-player``：CSR用のAPI。プレミアリーグのトッププレイヤーのデータを返す
- ``/api/top-teams``：SSR用のAPI。プレミアリーグの順位を返す


### 1. MSWインストール
```
yarn add msw --dev
```

必要に応じて、[CDN](https://mswjs.io/docs/recipes/using-cdn)からMSWをインストールすることもできます。


### 2. モックデータを用意する

今回は、テスト用の例として、CSRとSSRで返すAPIモックを以下のように定義しました。


```mock.ts
// CSR用のモックデータ
const topPlayersMockResponse = {
  players: [
    { name: "Bukayo Saka", team: "Arsenal" },
    { name: "Mohamed Salah", team: "Livepool" },
    { name: "Cole Palmer", team: "Chelsea" },
  ],
};

// SSR用のモックデータ
const topTeamsMockResponse = {
  ranking: [
    { rank: 1, team: "Livepool" },
    { rank: 2, team: "Chelsea" },
    { rank: 3, team: "Arsenal" },
  ],
};

```


### 3. リクエストハンドラを作成する

リクエストハンドラは、どのリクエストをインターセプトして、レスポンスを返すかを記述する関数です！

以下のコードでは``/api/top-player``のレスポンスに``topPlayersMockResponse``、``/api/top-teams``のレスポンスに``topTeamsMockResponse``を返す様に記述しています！
```handler.ts

export const handlers = [
  // CSR用のGETリクエスト
  http.get("/api/top-players", () => {
    return HttpResponse.json(topPlayersMockResponse); 
  }),

  // SSR用のGETリクエスト
  http.get("/api/top-teams", () => {
    return HttpResponse.json(topTeamsMockResponse); 
  }),
];

```

### 4. Node.jsの統合

SSRを有効にする為には、Node.jsへの統合が必要になります。

https://mswjs.io/docs/integrations/node

まずは設定を行います。

```server.ts
import { setupServer } from "msw/node";
import { handlers } from "./handlers";

export const server = setupServer(...handlers);
```

次にAPIモックを有効にして行きます！

```msw.server.ts
import { defineNuxtPlugin, useRuntimeConfig } from "#app";
import { server } from "@/mocks/msw/server";

export default defineNuxtPlugin(async () => {
  const config = useRuntimeConfig();
  const useMsw = config.public.USE_MSW;
  if (useMsw) server.listen();
});
```

``useMsw``という変数がtrueの時に、APIモックが有効になります！


### 5. ブラウザの統合
CSR側も有効にするには、ブラウザへの統合も行なう必要があります。

https://mswjs.io/docs/integrations/browser


以下のコマンドで``mockServiceWorker.js``を生成します！
```
npx msw init <PUBLIC_DIR> --save
```


次に設定を行います！

```browser.ts
import { setupWorker } from "msw/browser";
import { handlers } from "./handlers";

export const worker = setupWorker(...handlers);
```

最後にモックを有効化したい時だけ、``worker.start``でService Workerをアクティブする様に記述して行きます！


```msw.client.ts
import { defineNuxtPlugin, useRuntimeConfig } from "#app";
import { worker } from "@/mocks/msw/browser";

export default defineNuxtPlugin(async () => {
  const config = useRuntimeConfig();
  const useMsw = config.public.USE_MSW;
  if (useMsw) {
    await worker.start();    
  }
});
```

### 6. scriptコマンドでmockを起動する

開発中にモックサーバーを簡単に起動できるよう、scriptコマンドを設定します。

```package.json
"scripts": {
  "dev": "nuxi dev",
  "mock": "USE_MSW=true nuxi dev",
}

```
これにより、``yarn mock``を実行すればサーバー側とクライアント側のAPIモックが有効化されます！


### 7. mockServiceWorker.jsは本番ビルドに含めない

mockServiceWorker.jsのファイル内をみると以下の内容が書いてあります。

- 自動生成ファイルだから修正しないでね！
- 本番ビルド時は含めないでね！

修正は必要ないので無視しますが、本番ビルド時に``mockServiceWorker.js``を含まないようにする処理は自前で書く必要があります！

一例として僕たちはNuxtのhooksを使って、本番ビルド時は生成後の``mockServiceWorker.js``を削除する方針を取りました！

```nuxt.config.ts

hooks: {
 
    "nitro:build:public-assets": async () => {
      const path = await import("path");
      const fs = await import("fs");

      const outputDir = path.resolve(__dirname, ".output/public");
      const fileToExclude = "mockServiceWorker.js";
      const filePath = path.join(outputDir, fileToExclude);
      if (fs.existsSync(filePath)) {
        fs.unlinkSync(filePath);
      }
    },
  },
```

削除のタイミングは、Nitroサーバーの構築前で``mockServiceWorker.js``生成後である``nitro:build:public-assets``を採用しています！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3935932/4568035c-9f19-bb81-1cc1-92764a4eeee9.png)


参考：Nuxtのライフサイクルについて

https://nuxt.com/docs/api/advanced/hooks#nuxt-hooks-build-time


さて、ここまでやればプロジェクトへの導入は完了です🙆

期待するデータが取得出来ているか確認しましょう！

```
yarn mock
```

ブラウザ側でMSWが有効になっていれば、devtoolのconsoleに以下の表示が出ます！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3935932/2550066c-2212-532f-8e87-acdb685544a9.png)

実際のコード上でモックAPI化した``/api/top-player``、``/api/top-teams``を叩くとモックで定義したデータが返って来ます！


## まとめ

この記事では、MSWを使ってAPIモックをプロジェクトに導入する方法を詳細に説明しました。
これにより、バックエンドの開発が進む前にフロントエンドの開発を開始し、様々なシナリオをテストしやすくなります。
MSWはブラウザとNode.jsの両方で動作し、開発の柔軟性を高めてくれます。