OpenAPI×React Query×fakerで爆速APIモック作成

目次

はじめに

本日はOpenAPIで定義したAPI定義について、半自動でAPIモックを作る方法をご紹介します。

初心者向けですが、対象読者は基本的なReactやRestful APIの知識がある前提とします。

APIモックを作る必要性は?

一つの機能をフロントエンドとバックエンドで別々に作成していく場合に、APIモックがあるとバックエンド側の実装を待つことなくフロントエンドの開発を進められることができます。
個人的にはバックエンドとフロントエンドで分けて開発するのはデメリットが多いなと思うのですが、教育にもそこまでコストかけられないなどあれば別々に開発できるような環境を整備する必要があると思います。

なぜこの組み合わせなのか

あわせて読みたい
orval orval is able to generate client with appropriate type-signatures (TypeScript) from any valid OpenAPI v3 or Swagger v2 specification, either in yaml or json for...

相性が良いです。
OrvalというReact Client 自動生成ツールを使用することでOpenAPIスキーマからクライアントコードを生成することが可能です。
Orvalは以下の機能を実現するために作られています。

  • Typescriptモデルの作成
  • httpクライアント作成
  • mswを用いたモック関数の作成

デフォルトの機能としてモック関数の生成が備わっているため自作でモック関数を作成するより遥かに作成工数を削減することが可能です。

環境

今回は検証環境のため、buildが爆速なviteを使用しようと思います。
yarn、npmでも問題ないです。

  1. pnpm
  2. vite v4
  3. react v18
    "@faker-js/faker": "^8.0.2",
    "@tanstack/react-query": "^4.35.0",
    "axios": "^1.5.0",
    "msw": "^1.3.0",
    "orval": "^6.17.0",
    "prettier": "^3.0.3",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"

資材インストール

pnpm addで対象のライブラリをインストールします。
orval、@faker-js/faker、mswは開発時のみ使用するライブラリなので -d オプションをつけてインストールします。

pnpm add -d orval @faker-js/faker msw
pnpm add @tanstack/react-query axios

サンプルAPI定義作成

/todosというパスでGETリクエストを送信すると以下のようなjson配列を返すAPIを定義します。

[{
   id: 1,
   title: "サンプルタイトル",
   description: "サンプル説明",
   isComplete: false
}]

openapi.yamlというファイルを以下のように作成します。

paths:
  /todos:
    get:
      summary: Returns a list of todos.
      description: Optional extended description in CommonMark or HTML.
      responses:
        '200': # status code
          description: A JSON array of user names
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ToDo'
components:
  schemas:
    ToDo:
      type: object
      title: ToDo
      properties:
        id:
          type: integer
        title:
          type: string
          maxLength: 64
          minLength: 1
        description:
          type: string
          maxLength: 256
          minLength: 1
        isComplete:
          type: boolean
      required:
        - id
        - title
        - isComplete

内容はシンプルで、/todosというgetリクエストが来た際にToDoモデルの配列を返すという内容になります。

コードの自動生成

Orvalからコードを自動生成

orvalとは先ほど定義したopenapi.yamlからコードを自動生成するライブラリになります。
openapi-generator-cliというライブラリでもopenapi.yamlファイルからコードの自動生成は可能ですが、orvalではどちらかというと必要最低限のclientコードのみ作成することが可能のため今回のような需要に適しております。

あわせて読みたい
orval orval is able to generate client with appropriate type-signatures (TypeScript) from any valid OpenAPI v3 or Swagger v2 specification, either in yaml or json for...


orvalの設定ファイルをルート直下に以下のように作成します。
orval.config.ts

あわせて読みたい

ポイントは以下です。

  • openapi.yamlファイルをinputに指定
  • targetで生成するフォルダを指定
  • clientはreact-queryで生成する
  • tags毎にファイルを自動生成する
  • prettierでコードフォーマットする
  • mockを作成する
import { defineConfig } from "orval";

export default defineConfig({
sample: {
    input: {
     target: "./openapi.yaml"
    },
    output: {
      target: "./src/api/",
      client: "react-query",
      mode: "tags",
      clean: true,
      prettier: true,
      mock: true,
    },
	}
});

scriptsを実行しやすいようにpackage.jsonのscriptsにorvalコマンドを定義しておきましょう。

    "orval": "orval --config ./orval.config.ts"


$ pnpm orval コマンドを叩きコードを自動生成します。

murakami@murakami-pc:~/openapi-and-faker$ pnpm orval

> openapi-and-faker@0.0.0 orval /home/murakami/openapi-and-faker
> orval --config ./orval.config.ts

🍻 Start orval v6.17.0 - A swagger client generator for typescript
sample: Cleaning output folder

outputで指定したフォルダにコードが自動生成されていることが確認できます。
OpenAPI定義でタグを指定していないためファイル名がdefault.tsとして作成されたのですが、タグでtodoなどをつけるとtodo.tsとしてファイルが作成されます。
openapi.yamlファイルは肥大化するのでタグをつけることをおすすめします。

自動生成されたコード

自動生成されたdefault.tsを開くと以下のようになっています。

export const getTodos = (
  options?: AxiosRequestConfig,
): Promise<AxiosResponse<ToDo[]>> => {
  return axios.default.get(`/todos`, options);
};

export const getGetTodosQueryKey = () => [`/todos`] as const;

export const getGetTodosQueryOptions = <
  TData = Awaited<ReturnType<typeof getTodos>>,
  TError = AxiosError<unknown>,
>(options?: {
  query?: UseQueryOptions<Awaited<ReturnType<typeof getTodos>>, TError, TData>;
  axios?: AxiosRequestConfig;
}): UseQueryOptions<Awaited<ReturnType<typeof getTodos>>, TError, TData> & {
  queryKey: QueryKey;
} => {
  const { query: queryOptions, axios: axiosOptions } = options ?? {};

  const queryKey = queryOptions?.queryKey ?? getGetTodosQueryKey();

  const queryFn: QueryFunction<Awaited<ReturnType<typeof getTodos>>> = ({
    signal,
  }) => getTodos({ signal, ...axiosOptions });

  return { queryKey, queryFn, ...queryOptions };
};

/**
 * @summary Returns a list of todos.
 */
export const useGetTodos = <
  TData = Awaited<ReturnType<typeof getTodos>>,
  TError = AxiosError<unknown>,
>(options?: {
  query?: UseQueryOptions<Awaited<ReturnType<typeof getTodos>>, TError, TData>;
  axios?: AxiosRequestConfig;
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } => {
  const queryOptions = getGetTodosQueryOptions(options);

  const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
    queryKey: QueryKey;
  };

  query.queryKey = queryOptions.queryKey;

  return query;
};

export const getGetTodosMock = () =>
  Array.from(
    { length: faker.datatype.number({ min: 1, max: 10 }) },
    (_, i) => i + 1,
  ).map(() => ({
    id: faker.datatype.number({ min: undefined, max: undefined }),
    title: faker.random.word(),
    description: faker.helpers.arrayElement([faker.random.word(), undefined]),
    isComplete: faker.datatype.boolean(),
  }));

export const getDefaultMSW = () => [
  rest.get('*/todos', (_req, res, ctx) => {
    return res(
      ctx.delay(1000),
      ctx.status(200, 'Mocked status'),
      ctx.json(getGetTodosMock()),
    );
  }),
];

簡単にですが一つ一つ見ていきましょう。

useGetTodos

axiosを用いてAPIアクセスするgetTodoをreac-queryの形式で使用することができます。
React Query形式でパス名・APIメソッドについて気にすることなく呼び出すことが可能になります。

第一引数が query?: UseQueryOptions<Awaited<ReturnType<typeof getTodos>>, TError, TData> となっているため、react-queryのオプションを第一引数で指定することが可能です。

  const { data } = useGetTodos({
    query: {
      onSuccess: () => {
        console.log('success');
      },
      onError: () => {
        console.log('success');
      },
    },
  });

getGetTodosMock

Todoの配列モックオブジェクトを作成します。
fakerライブラリからランダム値のデータが作成されます。

    id: faker.datatype.number({ min: undefined, max: undefined }),
    title: faker.random.word(),
    description: faker.helpers.arrayElement([faker.random.word(), undefined]),
    isComplete: faker.datatype.boolean(),

getDefaultMSW

rest.get(‘*/todos’)のリクエストが来た際にgetGetTodosMockメソッドを返却します。
一点注意が必要で、mswはworkerプロセスが起動しているときのみ/todosのAPIをモックします。
そのためgetTodosMockで定義したデータを返却させるにはmswのworkerプロセスを起動しなければいけません。

アプリケーション内でworker.startを呼び出します。
setupWorkerでgetDefaultMSWを呼び出し、worker.start()からworkerを起動します。

import { getDefaultMSW, useGetTodos } from './api/default';

import { setupWorker } from 'msw';

const worker = setupWorker(...getDefaultMSW());

worker.start();

mswのworkerプロセスセットアップ

mswのworkerプロセスを起動する為に、ワーカーファイルを作成する必要があります。

あわせて読みたい
Browser integration Set up Mock Service Worker in the browser.

Mock Service Workerは、リクエストの受信を担当するService Workerを登録することで、クライアントサイドで動作します。ただし、ワーカーのコードを自分で書く必要はなく、ライブラリが配布しているワーカーファイルをコピーして使います。

下記コマンドを実行します。

 $ pnpm msw init public/ --save

publicフォルダにmockServiceWorker.jsが作成されていることを確認します。

Reactで自動生成したコードを呼び出す

App.tsxを修正

以下のようにApp.tsxを修正し、先ほど自動生成したuseGetTodosメソッドを呼び出しましょう。

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { getDefaultMSW, useGetTodos } from './api/default';

import { setupWorker } from 'msw';
import React from 'react';

const worker = setupWorker(...getDefaultMSW());
worker.start();

function App() {
  const queryClient = new QueryClient();
  return (
    <QueryClientProvider client={queryClient}>
      <Todo />
    </QueryClientProvider>
  );
}

const Todo: React.FC = () => {
  const { data: response } = useGetTodos({
    query: {
      onSuccess: (res) => {
        console.log(res);
      },
      onError: () => {
        console.log('success');
      },
    },
  });
  return (
    <>
      {response?.data.map((item) => {
        return (
          <div>
            ID: {item.id}, Title: {item.title} , Description: {item.description}
          </div>
        );
      })}
    </>
  );
};

export default App;

$ pnpm dev コマンドからアプリを起動します。
アプリが立ち上がると以下のようにuseGetTodosで呼び出したdataが画面に映っているはずです!

これでfakerより自動で作成されたモックデータが返却されるようになりました。
今回は初回のため色々とコード紹介しましたが、次回以降はAPI定義を作成しuse○○という自動生成されたコードを呼び出すだけでAPIモックを使用することが可能です。


自動で生成されたモックデータも約に立つとは思いますが、画面によっては指定したIDを出したい、指定した長いタイトルを出したいなどの要望が出てくると思います。
そこで最後に自動で作成したデータではなく、指定したデータを返却するようにカスタマイズするtipsを紹介します。

Custom API Mockの作成

上記で作成したgetGetTodosMockとgetDefaultMSWとは別に、固定文言を返すgetGetCustomTodosMockとgetCustomMSWメソッドを作成します。

import { rest } from 'msw';

export const getGetCustomTodosMock = () =>
  Array.from({ length: 10 }, (_, i) => i + 1).map((_, index) => ({
    id: index,
    title: 'タイトル' + index,
    description: '説明' + index,
    isComplete: index % 2 === 0 ? false : true,
  }));

export const getCustomMSW = () => [
  rest.get('*/todos', (_req, res, ctx) => {
    return res(
      ctx.delay(1000),
      ctx.status(200, 'Mocked status'),
      ctx.json(getGetCustomTodosMock()),
    );
  }),
];

App.tsxのsetupWorkerでgetDefaultMSW()より前にgetCustomMSWを渡します。

const worker = setupWorker(...getCustomMSW(), ...getDefaultMSW());
worker.start();

これで好きなデータを返すモックAPIを作成できましたね!

まとめ

orvalとmswを使用することでモックデータを簡単に作成することができました。
モックデータを上手く作ることで効率よく開発を進めれたらと思います。

一応サンプルコードは以下です。

GitHub
GitHub - Mr-peipei/orval-api-mock Contribute to Mr-peipei/orval-api-mock development by creating an account on GitHub.
よかったらシェアしてね!
  • URLをコピーしました!

この記事を書いた人

数学科出身のSoftware Engineer
情報通信が好きなのでブログを活用して発信しています。

コメント

コメントする

目次
閉じる