どうも。最近はフロントエンド側のタスクが多くなったふじむーです。
仕事にて、バックエンド側ができるまでの間GraphQLサーバーをモックする仕組みを作ることになりました。
その実装の中での気づきや導入手順などをまとめていこうと思います。
GraphQLはなんぞやについては、今回割愛してます。
初のGraphQLのモック構築で、まだまだ発展途上です。
もし異なる点などありましたら、アドバイスいただけると幸いです!
環境
Nuxt.js(2系) / TypeScript
Apollo Client(GraphQLを使用するためのライブラリ)
結論
MockServiceWorker(以下MSW)を使用することにしました。
https://mswjs.io/docs/
MSWは別途ポートを開けてサーバーを立てる必要もなく、
定義したモックファイルをimportしていけばモックできるので非常に手軽でした。
モックで返す値を設定したり、返却する時間を遅らせたりと、いろいろできそうでした。
公式のチュートリアルに沿っていけば導入はできそうです。
が、Nuxt用ににちょっと設定する必要はありました。
(Nuxt.jsだけチョット省かれてた...)
MSWとは
MSWとは、モックサーバーっぽいことをしてくれるライブラリです。
実際のリクエストをネットワークレベルで監視してくれて、該当するリクエストが来たら
定義されたデータを返します。
ライブラリなので、特にポートを開けてサーバーを立てるわけでもなく、手軽に導入できました。
MSWは、モックで返すデータや設定もいろいろ調整が可能です。
例えばこんなことができます。
- 返却するデータを任意に設定できる
- レスポンスするタイミングを遅らせる(ロード中の動作を確認できる)
- HTTPステータスを任意のものにできる
- エラーメッセージを任意のものに設定できる
また、console上でちゃんとモックできているかも確認できるので非常に便利です。
導入した理由
開発を進めるにあたって、バックエンド側はまだ未完成ですが、
フロントの開発を進める必要がありました。
そこで、フロント側としてどういうモックが求められるかを考慮し下記のように設定しました。
- 一覧画面のページネーションなどの確認のため、返却するデータ数を調整できるようにする
- あくまで補佐であり、モックを作ることに時間をかけすぎないようにする
そこから選定に入りました。
Apollo ServerとMSWで悩みましたが、
下記の特徴から、今回はMSWを導入することにしました。
- 既存のリポジトリ内でモックファイルを定義することができる
- 正常系、異常系に合わせて返却するデータを自由に設定できる
- 定義したモックは、MSWで指定された配列に入れるだけでモックできる
- リクエストをネットワークレベルで監視してくれるため、より本番環境に近い状態でモックできたりstorybookなどでも使えるようで拡張性がよさそう
MSWを導入
アーキテクチャ
公式に沿ってmocksというフォルダを作成し、そこにまとめています。
- mocks
- handlers
- queries // queryのリゾルバを定義したファイル
- mutations // mutationのリゾルバを定義したファイル
- handlers.ts // handlersで定義されたリゾルバをインポートしモックするファイル
- browser.ts
- handlers
- static
- mockServiceWorker.js
導入手順
公式ドキュメントに沿っていくと、わりとすんなり進められますが、
Nuxt.jsように設定する箇所がいくつかあったので合わせて記載していきます。
(ほとんど公式の抜粋です。)
今回はgraphqlでgetCarsというqueryをモックするとします。
1、まずはインストール
何はともあれインストールです。
$ npm install msw --save-dev
# or
$ yarn add msw --dev
####2、mocksフォルダを作成します
mocksのフォルダを作成します。
そして、mocks内にhandlers.tsを作成します。
このhandlers.tsに、MSWでモックするqueryやmutationを定義していきます。
####3、モックする内容を定義する
公式では handlers.tsのhandlers内に直接書いていますが、開発を進めるうちに記述量が膨大になりそうなので、handlersというフォルダを作成し、そこに書いていくことにしました。
モックでは下記を書きます。
- モックするクエリー名
- レスポンスの設定
書いたファイルはこんな感じです。
import { graphql } from 'msw'
/**
* リゾルバの定義
* @returns モックで返却する値や設定
*/
export const getCarsHandler = graphql.query<getCarsQuery>('getCars', (req, res, ctx) => {
return res(
ctx.data({
cars: [{
__typename: 'getCars',
id: 1,
name: 'R35 GT-R',
maker: '日産',
}],
}),
)
})
.queryの第一引数でモックするクエリー名を定義します。
これをもとにモックするかしないかをMSWが判断します。
第二引数では、レスポンスの設定です。
上記の状態で、carsのオブジェクトを返却します。
レスポンスの設定ではreq, res, ctxの引数があります。
- req リクエストに関する情報が入っている
- res モック応答を作成する関数
- ctx 現在のリクエストハンドラに固有の
それぞれの中身については下記ドキュメントから見られます。
https://mswjs.io/docs/basics/response-resolver
少し余談ですが、
返却するデータを任意にしたかったり、ロード中の動作も見たかったので下記のようにしてみました。
/**
* モックの返却値
* @returns {Object} getCarsで返却する値
* @returns {String} __typename query名
* @returns {Number} id 車のid
* @returns {String} name 車名
* @returns {String} maker メーカー
*/
const mockData = (): Cars => ({
__typename: 'getCars',
id: 1,
name: 'R35 GT-R',
maker: '日産',
})
/**
* 返却するデータ数
*/
const length = 100
/**
* リゾルバの定義
*/
export const getCarsHandler = graphql.query<getCarsQuery>('getCars', (req, res, ctx) => {
return res(
ctx.data({
cars: Array.from({ length: length }, mockData),
}),
// 2000ミリ秒遅らせてレスポンスする。(ロード中の動作などを確認できる。省略可)
ctx.delay(2000)
)
})
Array.fromで配列の長さと要素を指定できるので、それを使って返却するデータを変えられるようにしてみました。
ただ、これは同じ要素が指定した数になるので、それぞれ要素の値をバラバラにしたかったらFaker.jsなどを使うのが良いかもしれないです。
他にもレスポンスで使用できるプロパティがあります。
下記ドキュメントを参考していただけると幸いです。
https://mswjs.io/docs/api/response
####3、2で定義したモックをhandlers.tsにインポートします。
import { getCarsHandler } from '略'
/**
* MSWでモックするQuery、Mutationを定義するファイル
* 下記配列に含まれたハンドラがモックされる。
*/
export const handlers = [
getCarsHandler,
]
####4、mockServiceWorker.jsを自動生成します
公式では パブリックディレクトリーを対象に生成するとあります。
が、なぜかNuxt.jsのパブリックディレクトリには触れられておらず、、、
ということで調べてみることに。
翻訳してみると通常、サーバーのルートディレクトリですとのことでした。
Nuxtの公式を見てみると、どうやらstaticディレクトリがそれのようです。
https://nuxtjs.org/docs/directory-structure/static
ということで、staticディレクトリに作成します。
mockServiceWorker.jsはコマンドを打つと自動生成されます。
npx msw init static / --save
実行すると、mockServiceWorker.jsが自動生成され、
package.jsonに追記されます。
"msw": {
"workerDirectory": "static"
}
拡張子が.jsですが、自動生成されたファイルを見てみるとこのファイルは編集しないでくださいとコメントがあるので、なにも触れないことにしました。
/**
* Mock Service Worker (0.36.3).
* @see https://github.com/mswjs/msw
* - Please do NOT modify this file.
* - Please do NOT serve this file on production.
*/
####5、プラグインにして、MSWを動作させる
開発環境のみで動作させます。
import { worker } from '略'
/**
* MSWを起動させる定義ファイル
*/
export default () => {
if (process.env.NODE_ENV === 'development') {
worker.start()
}
}
導入してみた感想
まだ導入数日なので、参考になるかわかりませんが、、
結構手軽にモックできてよい!
初期の構築が終われば、その後はモックの定義とhandlers.tsへの定義で済むので手軽です。
(モックの定義もコピーすればほぼよいので、返す内容の定義とhandlersへの定義などで済む)
モックで定義していないクエリーに関しては、実際に設定したエンドポイントに見に行ってくれるので
結構長く使えそうな感じはしています。
参考
[MSW 導入] (https://mswjs.io/docs/getting-started)
[Nuxt公式] (https://nuxtjs.org/docs/directory-structure/static)
Mock Service Worker(msw)をNuxt.jsでモックサーバーとして使ってみる
Type-safe API mocking with Mock Service Worker and TypeScript
Array.from()