🔎
GraphQLのSchemaを元にサクッとモックサーバーを作る
概要
GraphQLでクライアントの開発をしており下記のような理由からモックサーバーが欲しくなりました。
- バックエンドの開発を待たずにGraphQLのSchemaベースで非同期でフロントエンドの開発を行いたい
- テスト用にAPIのモック
サクッと良い感じにモックサーバー建てられないかな〜と思って調べてみました。
作りたいもの
GraphQLのSchemaを正にして、可能な限り自動で型セーフなMSWのハンドラ+データ作成関数を作りたい。
これによってShemaの変更に対してモックサーバもすぐに追随できる+モックサーバのメンテナンスもしやすいのかなと思いました。
ざっくりと導入手順
下記にやったことをざっくりと書いていきます。
まず、下記のようなGraphQL SchemaとQueryがあると仮定します。
Schema
type Query {
getUser(id: ID!): User
}
type User {
id: ID!
name: String!
email: String!
}
Query
query GetUser($id: String!) {
getUser(id: $id) {
id
name
email
}
}
1. 必要なパッケージをインストール
まず初めに、必要なパッケージをインストールします。
npm install @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typescript-msw graphql-codegen-typescript-mock-data msw
それぞれの用途は下記です。
- GraphQL Code Generator: GraphQLのschema等からいろいろ自動生成してくれるライブラリ
- Mock Service Worker(msw): ネットワーク層でリクエストをインターセプトし、モックのレスポンスを返すことができるライブラリ
- @graphql-codegen/typescript-msw: クライアントで定義したquery/mutationから型セーフなmswのhandlerを自動生成する
- graphql-codegen-typescript-mock-data: Schemaからデータ自動生成するライブラリ
2. GraphQL Code Generatorの設定
次にcodegen.yml
ファイルを以下のように記述します。
const config: CodegenConfig = {
overwrite: true
schema: "path/to/your/schema.graphql" // 適当な値を入れる
documents: '**/*.graphql', // 適当な値を入れる
generates:
/**
* その他のgeneratorsは省略
*/
'__generated__/mockHandlers.ts': {
plugins: [
'typescript',
'typescript-operations',
'@graphql-codegen/typescript-msw',
{
'graphql-codegen-typescript-mock-data': {
prefix: 'mock',
},
},
],
},
}
この設定により、コード生成コマンドを実行するとTSの型
、MSWのハンドラ
、そしてモックデータ生成関数
がそれぞれ記載された下記のようなファイルが生成されます。
自動生成されたファイルの内容
import { graphql, ResponseResolver, GraphQLRequest, GraphQLContext } from 'msw'
export type Maybe<T> = T | null;
export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
export type MakeEmpty<T extends { [key: string]: unknown }, K extends keyof T> = { [_ in K]?: never };
export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
ID: { input: string; output: string; }
String: { input: string; output: string; }
Boolean: { input: boolean; output: boolean; }
Int: { input: number; output: number; }
Float: { input: number; output: number; }
};
export type Query = {
__typename?: 'Query';
getUser?: Maybe<User>;
};
export type QueryGetUserArgs = {
id: Scalars['ID']['input'];
};
export type User = {
__typename?: 'User';
email: Scalars['String']['output'];
id: Scalars['ID']['output'];
name: Scalars['String']['output'];
};
export type GetUserQueryVariables = Exact<{
id: Scalars['ID']['input'];
}>;
export type GetUserQuery = { __typename?: 'Query', getUser?: { __typename?: 'User', id: string, name: string, email: string } | null };
/**
* @param resolver a function that accepts a captured request and may return a mocked response.
* @see https://mswjs.io/docs/basics/response-resolver
* @example
* mockGetUserQuery((req, res, ctx) => {
* const { id } = req.variables;
* return res(
* ctx.data({ getUser })
* )
* })
*/
export const mockGetUserQuery = (resolver: ResponseResolver<GraphQLRequest<GetUserQueryVariables>, GraphQLContext<GetUserQuery>, any>) =>
graphql.query<GetUserQuery, GetUserQueryVariables>(
'GetUser',
resolver
)
export const mockQuery = (overrides?: Partial<Query>): Query => {
return {
getUser: overrides && overrides.hasOwnProperty('getUser') ? overrides.getUser! : mockUser(),
};
};
export const mockUser = (overrides?: Partial<User>): User => {
return {
email: overrides && overrides.hasOwnProperty('email') ? overrides.email! : 'sunt',
id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : 'a5756f00-41a6-422a-8a7d-d13ee6a63750',
name: overrides && overrides.hasOwnProperty('name') ? overrides.name! : 'porro',
};
};
3. 自動生成生成されたコードを使用する
後は下記のように作成されたコードを組み合わせるだけとなります。
-
mockGetUserQuery
: 自動生成されてたmswのハンドラー -
mockUser
: 自動生成されたダミーデータ作成関数
※ ダミーデータ作成関数は当然オーバーライド可能で、より現実的な値を設定することもできます
import { mockGetUserQuery, mockUser } from '__generated__/mockHandlers'
const handlers = [
mockGetUserQuery((req, res, ctx) => {
const { id } = req.variables;
return res(
// Schemaに応じて適当なデータが作成される
ctx.data({ mockUser() })
)
})
]
const worker = setupWorker(...handlers)
Discussion