フロントエンド開発でMock Service WorkerをサーバサイドAPIモックとして使う

## 概要
- サーバサイドAPIはまだ実装されてないけど、Webフロントエンドの実装を進めたい
- 顧客に見せるために作成したモックの実装を、できるだけ実際の実装に流用したい
- E2Eテストの為だけにビジネスロジックとは関係のない実装を入れたくない
- Mock Service Workerを使えばそんなお悩みを解決できます

## Mock Service Workerとは

Service Worker APIを利用してブラウザのリクエストをインターセプトすることで、サーバサイトAPIの振る舞いを模倣するモックを構築できるフレームワークです。[JSON Server](https://www.npmjs.com/package/json-server)のようなモックサーバフレームワークとの違いは、単独のサーバを起動するのではなく、Service Workerを常駐してブラウザのリクエストのインターセプトすることでモック用のレスポンスを返却する仕組みになっている点です。

[MDN - Service Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API)

## 準備
React用のサンプルが用意されているので、今回はそれを使用して挙動を見ていきたいと思います。

[Mock Service Worker](https://github.com/mswjs/msw) (以下MSW) の[Examples](https://github.com/mswjs/examples/tree/master/examples/rest-react)に記載されているインストール手順に従ってローカルに環境を構築します。

```sh
$ git clone https://github.com/mswjs/examples.git
$ cd examples
$ yarn
$ cd rest-react

$ yarn start
```
サービスが起動しました。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2221418/88f3a6b9-e26b-ca50-6a8c-1ca3e51d2c9b.png)
ブラウザから http:/localhost:3001 にへアクセスするとデモ画面が表示されます。
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2221418/a1759aa9-e7b7-4e5e-8512-7132e360326e.png)
## 動かしてみる

usernameを入力するとログインAPIが実行されて、そのレスポンス内容が画面に表示されるというデモになります。
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2221418/0df6e11c-b525-49d1-2cf6-45e17818f44f.png)
usernameを入力してSubmitボタンを押してみます。
![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2221418/c3e97d40-c627-e999-85eb-23541d2a9b85.png)
ユーザー情報が画面に表示されました。この値はMSWによって実行されているログインAPIのモックから返却されたものになります。
でも せっかく入力した username が画面に表示されていません。<br>コードを読み進めてゆくついでに画面に username が表示されるようにしていこうと思います。

## MSWの実行

デモのReactプロジェクトでは **examples/rest-react/src/index.js** の中でMSWを実行しています。このわずか数行の実装でブラウザからのリクエストがMSWへインターセプトされるようになります。
```js
// Start the mocking conditionally.
if (process.env.NODE_ENV === 'development') {
  const { worker } = require('./mocks/browser')
  worker.start()
}
```

## Reactのfetch処理

ReactではログインAPIをfetch APIでリクエストしています。ホストを指定していないので、ログインAPIは同ホスト内で実行してされている想定となっています。<br>JSON Serverなどを使用する場合はfetch APIに指定するホストなどを挿げ替える判定処理を施したりしますが、冒頭に記載した通りMSWではそのような必要はありません。

*examples/rest-react/src/LoginForm.js*
```js
// Handle a submit event of the form
const handleFormSubmit = useCallback(
(event) => {
    // Prevent the default behavior, as we don't want
    // for our page to reload upon submit.
    event.preventDefault()

    // Perform a POST /login request and send the username
    fetch('/login', {
    method: 'POST',
    body: JSON.stringify({
        username,
    }),
    })
    .then((res) => res.json())
    // Update the state with the received response
    .then(setUserData)
},
[username],
)
```

## MSW のコード

examples/rest-react/src/mocks 以下にMSWに関するソースコードが格納されています。<br>モックAPIの実態は handlers.js に実装されています。

/login というエンドポイントへpost methodでリクエストされた場合に、固定のJSONを返却するという処理になります。ちなみにこのコードには画面から入力された username を正しく取得できないバグがあるので、後ほど修正していきたいと思います。

*examples/rest-react/src/mocks/handlers.js*
```js
import { rest } from 'msw'

export const handlers = [
  rest.post('/login', (req, res, ctx) => {
    const { username } = req.body

    return res(
      ctx.json({
        id: 'f79e82e8-c34a-4dc7-a49e-9fadc0979fda',
        username,
        firstName: 'John',
        lastName: 'Maverick',
      }),
    )
  }),
]
```

## username を画面に表示できるようにしてみる

前述の通り、ソースコードにはバグがあるので username がレスポンスでundefinedになってしまいまうため、コードを少々修正します。

*examples/rest-react/src/mocks/handlers.js*

Before
```js
const { username } = req.body
```
After
```js
const { username } = JSON.parse(req.body)
```

次に画面にレスポンスで取得した username を表示するようにしてみます。

*examples/rest-react/src/LoginForm.js*

Before
```jsx
  if (userData) {
    return (
      <div>
        <h1>
          <span data-testid="firstName">{userData.firstName}</span>{' '}
          <span data-testid="lastName">{userData.lastName}</span>
        </h1>
        <p data-testid="userId">{userData.id}</p>
      </div>
    )
  }
```
After

```jsx
  if (userData) {
    return (
      <div>
        <h1>
          <span data-testid="firstName">{userData.firstName}</span>{' '}
          <span data-testid="lastName">{userData.lastName}</span>
        </h1>
        {/** 入力した username を表示してみる */}
        <p data-testid="username" style={{ color: 'red' }}>
          {userData.username}
        </p>
        <p data-testid="userId">{userData.id}</p>
      </div>
    )
  }
```

## 実行してみる

usernameを入力してSubmitボタンを押してみます。
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2221418/10d5d888-80b0-e03f-99f3-4570309a9128.png)
ログイン後の画面に入力した username が赤文字で表示されました。
![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2221418/8d3dd4fd-cca3-321d-33f5-c9fb8e30d1fb.png)
## 最後に

今回はMSWのデモを利用して簡単な仕組みを見ていきました。MSWを使用することでクライアントアプリケーションのコードに変更を加えることなくサーバサイドのモックと連携できることがわかりました。
次回は同デモを使ってE2Eテストの実装を見ていきたいと思います。

## 参考

https://github.com/mswjs/msw

https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API

https://www.npmjs.com/package/json-server