【Next.js】AppRouterでAPIレスポンスをMSWでモック化する
IT技術
MSWとは
Mock Service Workerという名前で、ブラウザと Node.js 用の API モック ライブラリです。
開発環境でサーバー側にAPIを用意できない場合や、Jest実行時に利用することで柔軟に実装することができます。
REST APIやGraphQL APIのモック化が可能で、今回はGrahpQLのほうを解説していきます。
利用したバージョン
1"dependencies": {
2 "next": "14.2.5",
3 "react": "^18",
4 "react-dom": "^18"
5},
6"devDependencies": {
7 "@graphql-codegen/cli": "5.0.2",
8 "@graphql-codegen/client-preset": "4.3.2",
9 "@graphql-codegen/typescript-graphql-request": "^6.2.0",
10 "@types/node": "^20",
11 "@types/react": "^18",
12 "@types/react-dom": "^18",
13 "eslint": "^8",
14 "eslint-config-next": "14.2.5",
15 "msw": "^2.3.2",
16 "postcss": "^8",
17 "tailwindcss": "^3.4.1",
18 "typescript": "^5"
19},
GraphQL APIサーバー
Mock化するので準備しなくても良いのですが、過去の記事で実装してたので流用します!
Ruby on Rails & GraphQLの環境構築と実装
今回利用するSchemaは以下の通りです。
1# query
2 users: [User!]!
3
4# mutation
5 addUser(input: AddUserInput!): AddUserPayload
単純にuserを登録したり、user一覧を取得できるだけのものですね。
AppRouterを使ったページの準備
本題ではないのである程度省略して説明しますが、以下リポジトリにコードをまとめたので細かい箇所はこちらで確認してください。
https://github.com/ryuto-imai/mock-graphql
GraphQL Schemaの取り込み
GraphQL-Codegenをインストール・セットアップをして、今回利用するGraphQLクライアントのために以下をインストールしましょう。
npm install -D @graphql-codegen/typescript-graphql-request
codegen.ymlを以下のようにしてください。
1overwrite: true
2schema: "http://localhost:3000/graphql" # GraphQLサーバーのURL
3documents: "graphql/*/*.graphql"
4generates:
5 graphql/generated/schema.ts:
6 plugins:
7 - 'typescript'
8 - 'typescript-operations'
9 - 'typescript-graphql-request'
10 config:
11 withHooks: false
あとはdocumentsに設定したpathにgraphqlファイルを適当につくって、npm run codegen
を実行するだけです。
GraphQL クライアント
簡易的に実装できるようにgraphql-requestを利用しています。
1import { GraphQLClient, RequestDocument, Variables } from 'graphql-request'
2
3const host = 'http://localhost:3000' // GraphQLサーバーのURL
4const client = new GraphQLClient(host + '/graphql')
5
6export const graphqlRequest = async (
7 document: RequestDocument,
8 variables?: Variables
9) => {
10 try {
11 return await client.request(document, variables)
12 } catch (error) {
13 console.error(error)
14 alert(error)
15 }
16}
呼び出す時は以下の感じです。
await graphqlRequest(AddUserDocument, { input: newUser })
User Schemaを扱うページ
以下のような入力フォームと、User一覧を表示する簡単なページを実装しました。(chatgptが
ページ表示時にquery usersを呼んでUsersContextに値を渡しています。
そしてUsersではUsersContextから値を表示し、
Add Userボタンのクリックでmutation addUserが呼ばれてUsersContextの値を更新しています。
usersはサーバー上で呼ばれ、addUserはブラウザ上で呼ばれる感じですね。
サーバー上の通信をモック化
まずはサーバー上で通信するquery usersのモック化からしていきます。
公式サイトにあるようにMSWをインストールしましょう。
npm install msw@latest --save-dev
次にquery usersのhandlerを用意して、
1import { UsersDocument } from '@/graphql/generated/schema'
2import { graphql, HttpResponse } from 'msw'
3
4export const userHandlers = [
5 graphql.query(UsersDocument, () => {
6 return HttpResponse.json({
7 "data": {
8 "users": [
9 {
10 "id": "1",
11 "name": "hoge",
12 "age": 20,
13 "createdAt": "2024-07-19T02:17:19Z",
14 "updatedAt": "2024-07-19T02:17:19Z"
15 },
16 {
17 "id": "2",
18 "name": "foo",
19 "age": 30,
20 "createdAt": "2024-07-20T03:47:45Z",
21 "updatedAt": "2024-07-20T03:47:45Z"
22 }
23 ]
24 }
25 })
26 }),
27]
それをsetupServerに渡します。
1import { userHandlers } from './handler'
2import { setupServer } from 'msw/node'
3
4export const server = setupServer(...userHandlers)
あとはpageファイルなどでserver.listen()
を呼び出すだけ!
1import { server } from "@/mocks/graphql/server";
2
3process.env.NODE_ENV === 'development' && server.listen()
4
5export default async function Home() {
6...
ページを表示した時、handlerに設定した値がUsersに表示されるようになりました!
GraphQLサーバーを落としていても問題なく表示されます。
ブラウザ上の通信をモック化
次にブラウザ上で通信するmutation addUserのモック化をしていきましょう。
query usersと同様に、mutation addUserをhandlerに追加します。
1 ...
2 graphql.mutation(AddUserDocument, () => {
3 return HttpResponse.json({
4 "data": {
5 "addUser": {
6 "user": {
7 "id": "3",
8 "name": "fuga",
9 "age": 40,
10 "createdAt": "2024-07-21T04:47:45Z",
11 "updatedAt": "2024-07-21T04:47:45Z"
12 }
13 }
14 }
15 })
16 })
17]
なんとなくこれでいけそうな気はしますが、server.listen()
はサーバーでの通信しか対応できていません…
ブラウザ上でモックする場合はsetupWorkerを利用する必要があります。
まずはワーカースクリプトを生成します。
npx msw init <PUBLIC_DIR> --save
<PUBLIC_DIR>の部分は、静的ファイルを置くpublicフォルダを指定すればOKです!
あとはsetupServerと同様な感じでsetupWorkerを実装して、
1import { setupWorker } from 'msw/browser'
2import { userHandlers } from './handler'
3
4export const worker = setupWorker(...userHandlers)
あとはこれを呼び出すだけなのですがブラウザ上で呼び出す都合上、少し工夫が必要になります。
やり方は色々あると思いますが、
Mock用のコンポーネントを用意してuseEffectでworkerを呼び出し、
1'use client'
2
3import { useEffect } from "react"
4
5export const MswWorker = () => {
6 useEffect(() => {
7 if (process.env.NODE_ENV !== 'development' || typeof window === 'undefined') {
8 return
9 }
10
11 import('@/mocks/graphql/browser').then(({ worker }) => {
12 worker.start()
13 })
14 })
15 return <></>
16}
pageファイルにコンポーネントを設置する方法を採用しました!
1export default async function Home() {
2 const data = await graphqlRequest(UsersDocument)
3
4 return (
5 <main>
6 <MswWorker />
7 ...
これによりGraphQLサーバーが落ちている状態でも、
Add Userのフォームによってモック化した値がUsersテーブルに表示されるようになりました!
Tips: JestでMSWを使う
Jestでのモック化は、サーバー上の通信をモック化の処理を流用することができます。
setupFilesAfterEnvに設定したファイルで以下を実行して、
1const { server } = require('~/tests/mocks/msw/server')
2beforeAll(() => server.listen())
3afterEach(() => server.resetHandlers())
4afterAll(() => server.close())
テストケースごとにモックするデータを変えるためにserver.useを使いましょう!
1server.use(
2 graphql.query(UsersDocument, () => {
3 return HttpResponse.json({
4 ...
5 })
6 })
7)
まとめ
MSWを使うと結構簡単にモック化できることがわかっていただけたかと思います。
GraphQLサーバーにあまり依存せずに実装できるようになるので、是非導入してみてください!
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ
おすすめ記事
immichを知ってほしい
2024.10.31