前回、TypeSpecを使ってOpenAPI定義を作成し、Orvalを使ってOpenAPI定義から、SWRのAPIフックを自動生成した。
今回は、その自動生成したAPIフックのMSW(Mock Service Worker)のモック機能を活用し、 StorybookからMSWのモックAPIに対してリクエストを行う方法をまとめる。
MSWとは
MSW(Mock Service Worker)はWEBアプリケーション向けのモックライブラリで、 これを使うとバックエンドAPIをモックしてフロントエンドの開発を進めることができる。
MSWはブラウザのService Workerを使用し、ブラウザ内でAPIへのリクエストをインターセプトしてモックレスポンスを返すことができる。
他の方法に比べて、以下の利点がある。
- コード内でモックに差し替える方法に比べると、モック用の追加の実装を用意する必要がないので、コードがシンプルになる
- JSON Serverなどの方法に比べると、フロントサーバーと別プロセスでモックサーバーを立てたりする必要がなく、環境をシンプルに保てる
なお、サーバーサイド(Node.js環境)でも同じように動作させられ、 その場合、Service Workerの代わりにNode.jsのhttpモジュールをインターセプトする。
Storybook
Storybookを使うと、UIコンポーネントを独立して開発することができる。
Next.js 13では、MSWと相性が悪いようで、実際にNext.jsアプリケーションを立ち上げて、モックデータを取得することができなかった。(以下のIssueのコメントよると、Pages routerでもApp routerでも動作しないとのこと)
今回のプロジェクトでは、Storybookをベースにコンポーネントを作成しており、 Storybook上でMSWを利用できれば十分だったので、StorybookからMSWを使う方法をまとめる。
Storybookへの追加
msw用のアドオンが利用できる。
以下のコマンドでインストールする。
npm i msw msw-storybook-addon -D
Service workerの作成
Service Worker用のワーカースクリプトを動作させるために、ワーカースクリプトをpublic/
ディレクトリに追加する。
npx msw init public/
.storybook/preview.ts
の修正
以下のアドオン設定を追加する。
handlers
は、この後作成する。
+ import { initialize, mswLoader } from "msw-storybook-addon";
+ import { handlers } from "../mocks/handlers";
+ // Initialize MSW
+ initialize();
const preview: Preview = {
parameters: {
+ msw: {
+ handlers
+ }
},
+ loaders: [mswLoader]
};
OrvalでMSWモックを生成
orval.config.ts
へ追記
前回の記事の設定に、mock: true
を追加して、
SWRファイルの作成と一緒にモックも作成するように変更する。
module.exports = {
"user-api": {
input: {
target: "./openapi.yml",
},
output: {
mode: "split",
target: "./api/endpoints",
schemas: './api/models',
client: "swr",
+ mock: true,
},
hooks: {
afterAllFilesWrite: "prettier --write",
},
},
};
userAPI.msw.ts
の生成
モックの生成を有効化した後、npx orval
を実行すると、以下のuserAPI.msw.ts
が生成される。
mswのモック定義が自動的に生成されていることがわかる。
モックデータは、@faker-js/faker
を使って、ランダムなデータが生成している。
/**
* Generated by orval v6.31.0 🍺
* Do not edit manually.
* User API
* OpenAPI spec version: 0.0.0
*/
import {
faker
} from '@faker-js/faker'
import {
HttpResponse,
delay,
http
} from 'msw'
import type {
User
} from '../models'
export const getUsersGetUsersResponseMock = (): User[] => (Array.from({ length: faker.number.int({ min: 1, max: 10 }) }, (_, i) => i + 1).map(() => ({email: faker.word.sample(), id: faker.word.sample(), name: faker.word.sample()})))
export const getUsersGetUserResponseMock = (overrideResponse: Partial< User > = {}): User => ({email: faker.word.sample(), id: faker.word.sample(), name: faker.word.sample(), ...overrideResponse})
export const getUsersGetUsersMockHandler = (overrideResponse?: User[] | ((info: Parameters<Parameters<typeof http.get>[1]>[0]) => Promise<User[]> | User[])) => {
return http.get('*/users', async (info) => {await delay(1000);
return new HttpResponse(JSON.stringify(overrideResponse !== undefined
? (typeof overrideResponse === "function" ? await overrideResponse(info) : overrideResponse)
: getUsersGetUsersResponseMock()),
{
status: 200,
headers: {
'Content-Type': 'application/json',
}
}
)
})
}
export const getUsersGetUserMockHandler = (overrideResponse?: User | ((info: Parameters<Parameters<typeof http.get>[1]>[0]) => Promise<User> | User)) => {
return http.get('*/users/:id', async (info) => {await delay(1000);
return new HttpResponse(JSON.stringify(overrideResponse !== undefined
? (typeof overrideResponse === "function" ? await overrideResponse(info) : overrideResponse)
: getUsersGetUserResponseMock()),
{
status: 200,
headers: {
'Content-Type': 'application/json',
}
}
)
})
}
export const getUserAPIMock = () => [
getUsersGetUsersMockHandler(),
getUsersGetUserMockHandler()
]
mocks/handlers.ts
の追加
mocks
ディレクトリを作成する。
mkdir mocks
cd mocks
先ほど作成したgetUserAPIMock
を使って、mocks/handlers.ts
にモックAPIのハンドラを追加する。
+ import { getUserAPIMock } from "../api/userAPI.msw.ts";
+
+ export const handlers = getUserAPIMock();
このhanlders
が.storybook/preview.ts
のmswのhandlers
の設定に渡され、
Storybook上でMSWのモックAPIを利用できるようになる。
コンポーネントの利用方法は、前回と同様、生成されたSWRのカスタムフックをReactコンポーネントで呼び出せば良い。 内部的には、MSWのモックAPIが呼び出される。
const { data, error, isLoading } = useUsersGetUsers();
モックデータをカスタムしたい場合
stringで定義した場合、faker.word.sample()
モックをカスタムしたい場合は、以下のようにproperties
オプションでカスタムすることができる。
https://orval.dev/reference/configuration/output#mock
たとえば、名前、Email、画像URLなどをランダムなデータにしたい場合は、以下のように設定する。
module.exports = {
"user-api": {
input: {
target: "./openapi.yml",
},
output: {
mode: "split",
target: "./api/endpoints",
schemas: './api/models',
client: "swr",
mock: true,
+ override: {
+ mock: {
+ properties: {
+ name: () => faker.name.fullName(), // nameフィールドはランダムな名前を返すように変更
+ email: () => faker.internet.email(), // emailフィールドはランダムなメールアドレスを返すように変更
+ '/image/': () => faker.image.url(), // imageとつくフィールドは画像URLを返すように変更
+ },
+ }
+ },
hooks: {
afterAllFilesWrite: "prettier --write",
},
},
},
};
まとめ
最終的に開発の流れは以下の通りとなる。
- TypeSpecでAPI定義をtspファイルで作成
- tspファイルからOpenAPIのyamlファイルを自動生成
- Orvalを使って、OpenAPIのyamlファイルからSWRフックとMSWのモック実装を自動生成
- APIのMSWモックを使って、Storybook上でコンポーネントの開発を進める