mswを使ってライブラリも丸ごとテストしよう😃 - Mock Service Worker  入門

# やりたいこと

ライブラリから外部の Web APIに通信している機能がありました。
どうやってテストしようか...🤔

そこで Mock Service Worker の登場です 🎉

msw を使って Jest でテストできるようにしていきます。

# mswとは

次世代の API モッキング
ネットワーク レベルでリクエストをインターセプト（横取り）してモックします。
テスト、開発、およびデバッグのために同じモック定義をシームレスに再利用します。

https://mswjs.io/

ネットワークレベルでモック化できるので、ライブラリの実装も含めてテストできて good 👍

# やること一覧

1. リクエストを確認する
1. ライブラリをインストールする
1. Service Workerを生成する
1. モックを作る
1. テストコードを書く

# リクエストを確認する

まずはモック化する対象を確認します。
デベロッパーツールの Network タブで Request URL, Request Method, Response を確認します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71897/2460fe80-9e5d-2ef9-df6e-b22fd5b22386.png" width="1000">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71897/e4206250-1296-8133-8fd2-f0937cd6b2c5.png" width="1000">

# ライブラリをインストールする

Jestで動かすには whatwg-fetch が必要なので一緒にインストールしておきます。

```
$ npm --save-dev install msw whatwg-fetch 
```

# Service Workerを生成する

msw が使用する Service Worker を生成します。

```
npx msw init ./public --save
```

# モックを作る

### ディレクトリを作る

モック関連のモジュールを管理するディレクトリを作成します。

```
mkdir src/mocks
```

### レスポンスを定義する

ファイルが肥大化しないように、エンドポイントごとにファイルを作成します。
例えば `https://***.com/api/items` のモックを作成する場合、`src/moks/api/items.ts` を作成します。

```src/moks/api/items.ts
import { ResponseResolver, MockedRequest, restContext } from 'msw';

export const get: ResponseResolver<MockedRequest, typeof restContext> = (
  _req,
  res,
  ctx
) => {
  return res(
    ctx.status(200),
    ctx.json([
        {
          id: 1,
          name: 'ミネラルウォーター',
          price: 150,
        },
        {
          id: 2,
          name: 'オレンジジュース',
          price: 200,
        },
      ]
    )
  );
};

```

### リクエストハンドラーを作る

rest[METHOD] にリクエストパスを指定して、リクエストハンドラーを作成します。

```src/mocks/handlers.ts
import { rest } from 'msw';
import * as items from './api/items';

const url = (path: string): string => {
  return new URL(path, 'https://***.com').toString();
};

export const handlers = [
  rest.get(url('api/items'), items.get),
];
```

### Nodeで動くように設定する

Jest でモックを使うのでサーバー （Node） で動くように設定します。

`src/mocks/server.ts` を作成します。
`server.ts` では、リクエストハンドラーを使用してサーバーインスタンスを作成します。

```src/mocks/server.ts
import { setupServer } from 'msw/node';
import { handlers } from './handlers';

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

これでモックの作成は終わりです😌☕️

# テストコードを書く

作ったモックを使用してテストコードを書いていきます。

`server.listen()` でモックサーバーをスタートします。
`server.resetHandlers()` で追加されたすべてのリクエストハンドラーを削除します。
`server.close()` でモックサーバーをクローズします。

```src/test/item.test.ts
import { rest } from 'msw';
import { server } from '../../mocks/server';
import 'whatwg-fetch';

describe('fetchItems', () => {
  beforeAll(() => server.listen());
  afterEach(() => server.resetHandlers());
  afterAll(() => server.close());

  test(...);
});
```

異常系のテストも書いていきます。

異なるレスポンスを定義したいときは server.use で新しいリクエストハンドラーを作成してサーバーインスタンスに追加してある必要があります。
`res.once` とすることでこのリクエストハンドラーは一度だけ実行されます。

```src/test/item.test.ts
...
  test('異常系', async () => {
    server.use(
      rest.get('https://***.com/api/items', (req, res, ctx) => {
        return res.once(
          ctx.status(500),
          ctx.json({ message: 'Internal Server Error' })
        );
      })
    );
    ...
  });
...
```

これで無事テストできました🙌

# 参考

https://mswjs.io/docs/

https://zenn.dev/azukiazusa/articles/using-msw-to-mock-frontend-tests