MSWを使ったフロントエンド開発の効率化

GMOメイクショップ コアグループでフロントエンドエンジニアをしている池田です。
主にTypescriptとVue.jsを用いた開発をしています。
当記事ではJS・TSでモックサーバーとして利用できるライブラリMSWの紹介と、フロントエンド開発での導入方法について触れていきたいと思います。

MSWとは

Mock Service Worker (MSW) はブラウザとNode.js用のAPIモッキング・ライブラリです。 MSWは発信されるリクエストをネットワークレベルでインターセプトし、モックされたレスポンスを返すことが出来ます。

どのようなケースで使えるか

  • 開発時のmockサーバーとして利用
  • APIリクエストを含むテストでの利用

開発時のmockサーバーとして利用

フロントエンドとバックエンドを同時に開発していて、バックエンドのAPI開発待ちになっている。 こんな状況になった時にMSWをモックサーバーとして利用し、バックエンドからのレスポンスをMSWで代わりに返すことが出来ます。

また、モックとして使用するデータは自分で好きなように定義できるので、DBのデータに希望するデータが無くて、 データを作るにもDBのリレーションが複雑なためデータ作成に時間がかかるというような場合でもMSWは有効です。

※ バックエンドとのインターフェイスは確定している、ほとんど決まっているという状態でないと手戻りになる可能性が高いので、そこは注意が必要かと思います。

APIリクエストを含むテストでの利用

APIリクエストを含むテストでDBと直接繋いだ場合、DBのデータの状態によってテストが安定してパスしないなんてことが起こり得ます。 モックサーバーを利用すれば、常に同じレスポンスを返すことが出来るため、APIリスエストを含むテストだとしても、安定してテストが成功します。

類似ライブラリとの比較

有名なライブラリにjson-serverなどがありますが、それらの比べて何が違うのか見てみましょう。 こちらから分かるようにGraphQLに対応可能なのはMSWのみです。(2023/12/21時点)

API
MSW json-server Mirage Nock
REST
GraphQL × × ×
環境
MSW json-server Mirage Nock
Node.js
ブラウザ ×

導入方法

MSWのバージョンは2.0以降を想定してます。
2.0より前のバージョンの場合はハンドラーの定義やワーカーの定義などで少し書き方が異なりますのでご注意ください。

インストール

npm install -D msw

ワーカースクリプトの生成

npx msw init <PUBLIC_DIR> --save

<PUBLIC_DIR>は例えば、プロジェクトのルート直下にpublicがある場合は ./public となります。 実行後に指定したディレクトリ配下にmockServiceWorker.jsが生成されています。

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

生成されたmockServiceWorker.jsを見てみると、冒頭にこんなコメントがあります。

/**
 * Mock Service Worker.
 * @see https://github.com/mswjs/msw
 * - Please do NOT modify this file.
 * - Please do NOT serve this file on production.
 */

というように書いてあるが、どのようにすれば良いのか?

vite環境での解決方法になりますが、vite.config.jsに下記のようなコードを追加しました。

function excludeMsw() {
  const path = require('path')
  const fs = require('fs')

  return {
    name: 'exclude-msw',
    resolveId(source) {
      return source === 'virtual-module' ? source : null
    },
    renderStart(outputOptions) {
      const outDir = outputOptions.dir
      const msWorker = path.resolve(outDir, 'mockServiceWorker.js')
      fs.rm(msWorker, () => console.log(`Deleted ${msWorker}`))
    },
  }
}
export default defineConfig(({ mode }) => {
  ~~ 中略 ~~
  return {
    plugins: [excludeMsw()]
  }
})

モックの定義

既に記載した通り、RESTとGraphQLの両方に対応しています。 それぞれ以下のように定義します。 型を指定すると、型推論が効くようになるので、レスポンスなど間違うこと無く記述出来るのが良いです。

REST
// handler.ts
import { http, HttpResponse } from 'msw'

export const handlers = [
  http.get<never, AuthReq, AuthRes>('/auth', async ({ request }) => {
    const req = await request.json()
    console.log(req.user.id)
    return HttpResponse.json({
      user: {
        id: '1',
        name: 'test',
        age: 20,
      },
    })
  }),
]

リクエストの型、レスポンスの型を指定することで型推論が効くようになります。

GraphQL
// handler.ts
import { graphql, HttpResponse } from 'msw'

export const handlers = [
  graphql.query<UserQuery, UserQueryVariables>('getUser', ({ variables }) => {
    console.log(variables.input.id)
    return HttpResponse.json({
      data: {
        user: {
          id: '1',
          name: 'test',
          age: 20,
        },
      },
    })
  }),
]

こちらも同様にgraphql-codegenなどから生成された型を指定することで型推論が効くようになります。

ワーカーを起動するコードを書く

// worker.ts
import { setupWorker } from 'msw/browser'

import { handlers } from '@/mocks/handlers'

export const worker = setupWorker(...handlers)
// main.ts
if (process.env.APP_DEPLOY_ENV === 'development') {
  const { worker } = await import('@/mocks/worker')
  await worker.start({ onUnhandledRequest: 'bypass' })
}

ここまで準備したらアプリケーションの起動をしてみます。
[MSW] Mocking enabled. が表示されていれば問題なく動いています。

リクエストを投げてみると、モックに定義したレスポンスが返ってきます!

最後に

モックを利用することで開発の動作確認、自動テストの実行、確認が難しいデータパターンやエラーの再現など様々なケースで役に立つと思います。
ぜひともMSWを利用してフロントエンドの開発速度を上げていきましょう!

参考

mswjs.io

github.com