GraphQLのSchemaを元にサクッとモックサーバーを作る

## 概要

GraphQLでクライアントの開発をしており下記のような理由からモックサーバーが欲しくなりました。

- バックエンドの開発を待たずにGraphQLのSchemaベースで非同期でフロントエンドの開発を行いたい
- テスト用にAPIのモック

サクッと良い感じにモックサーバー建てられないかな〜と思って調べてみました。


## 作りたいもの

GraphQLのSchemaを正にして、可能な限り自動で型セーフなMSWのハンドラ＋データ作成関数を作りたい。
これによってShemaの変更に対してモックサーバもすぐに追随できる＋モックサーバのメンテナンスもしやすいのかなと思いました。

## ざっくりと導入手順

下記にやったことをざっくりと書いていきます。

まず、下記のようなGraphQL SchemaとQueryがあると仮定します。

### Schema

```graphql
type Query {
  getUser(id: ID!): User
}

type User {
  id: ID!
  name: String!
  email: String!
}
```

### Query

```graphql
query GetUser($id: String!) {
  getUser(id: $id) {
    id
    name
    email
  }
}
```

### 1. 必要なパッケージをインストール

まず初めに、必要なパッケージをインストールします。

```bash
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](https://the-guild.dev/graphql/codegen): GraphQLのschema等からいろいろ自動生成してくれるライブラリ
- [Mock Service Worker(msw)](https://mswjs.io/): ネットワーク層でリクエストをインターセプトし、モックのレスポンスを返すことができるライブラリ
- [@graphql-codegen/typescript-msw](https://the-guild.dev/graphql/codegen/plugins/typescript/typescript-msw): クライアントで定義したquery/mutationから型セーフなmswのhandlerを自動生成する
- [graphql-codegen-typescript-mock-data](https://github.com/ardeois/graphql-codegen-typescript-mock-data): Schemaからデータ自動生成するライブラリ


### 2. GraphQL Code Generatorの設定

次に`codegen.yml`ファイルを以下のように記述します。

```ts
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のハンドラ`、そして`モックデータ生成関数`がそれぞれ記載された下記のようなファイルが生成されます。


<details><summary>自動生成されたファイルの内容</summary><div>

```typescript
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',
    };
};
```
</div></details>


## 3. 自動生成生成されたコードを使用する

後は下記のように作成されたコードを組み合わせるだけとなります。

- `mockGetUserQuery`: 自動生成されてたmswのハンドラー
- `mockUser`: 自動生成されたダミーデータ作成関数

※ ダミーデータ作成関数は当然オーバーライド可能で、より現実的な値を設定することもできます

```typescript
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)
```