0
0

More than 1 year has passed since last update.

Storybookでaxios-mock-adapterを使ってAPIコールをモック

Posted at

背景

Storybookを導入して、動作確認をする時に、API呼び出しを含む処理もあったので、Mockのやり方を確認した。
今回は今まで使い慣れてきたaxios-mock-adapterをStorybookに取り入れる方法を紹介する。
今後、mswも試したいと思う。

Storybookの導入

インストール

インストールは下記公式サイトの手順通りに実施する。
https://storybook.js.org/docs/react/get-started/install

トラブルシューティング

  • このissueが出ていたが、npx storybook initの一部として、npm7 migrationするかどうか聞かれた場合、[Y]を選択すれば解決される。
  • react-scriptsのバージョンに気をつけよう。storybook 6.xを使う場合はreact-scripts 5にバージョンアップする必要がある。でなければ、storybookの起動時に失敗する。

axios-mock-adapter

今回紹介するサンプルコードをこちらで公開している。

AxiosMockコンポーネント

import { useEffect } from "react";
import MockAdapter from "axios-mock-adapter";
import { axiosInstance } from "../apis/axios-instance";

interface IProps {
  mockApi: (adapter: MockAdapter) => void;
}

const axiosMock = new MockAdapter(axiosInstance);

/**
 * StoryBookのstoriesでaxios mockApi adapterを利用するためのコンポーネント
 * 
 * 
 * Usage:
   export const Default = () => {
    const mockApi = (axiosMock: MockAdapter) => {
      axiosMock.onGet('/api/meetings/1').reply(200, {
        id: 1,
        title: 'A Meeting',
      });
    };
    return (
      <>
        <AxiosMock mockApi={mockApi} />
        <Meeting />
      </>
    );
  };
 * @param mockApi モックの内容を定義するメソッド
 * @see https://gist.github.com/rafaelrozon/ed86efbea53094726e162dc05882cddc
 */
function AxiosMock({ mockApi }: IProps) {
  useEffect(() => {
    mockApi(axiosMock);
    return () => {
      axiosMock.reset();
    };
  }, [mockApi]);
  return <></>;
}

export default AxiosMock;

Customer.stories.tsx

下記コードで示すように、{mockApi && <AxiosMock mockApi={mockApi} />}を使ってMockがある時に、AxiosMockコンポーネントを挿入するようにしている。
AxiosMockがレンダリングされると、AxiosMockのuseEffectで定義しているMockの登録処理が実行されるので、APIのMockが登録される。

下記の例では、コンポーネントで手動確認するためのStoryとInteraction Testを実装してある。

import { ComponentMeta, ComponentStory } from "@storybook/react";
import { GlobalContextProvider } from "../../contexts/GlobalContext";
import { MemoryRouter } from "react-router-dom";
import { within, userEvent, waitFor } from "@storybook/testing-library";
import { expect } from "@storybook/jest";
import Customer from "./Customer";
import MockAdapter from "axios-mock-adapter/types";
import AxiosMock from "../../test-utils/AxiosMock";

export default {
  title: "Pages/Customer",
  component: Customer,
} as ComponentMeta<typeof Customer>;

const Template = (mockApi?: (axiosMock: MockAdapter) => void) => (
  <GlobalContextProvider>
    <MemoryRouter>
      {mockApi && <AxiosMock mockApi={mockApi} />}
      <Customer />
    </MemoryRouter>
  </GlobalContextProvider>
);

export const Default: ComponentStory<typeof Customer> = () => {
  const mockApi = (axiosMock: MockAdapter) => {
    axiosMock.onGet("/api/v1/address?postcode=1840014").reply(200, {
      address: "東京都XXXXXX",
    });
  };
  return Template(mockApi);
};
Default.storyName = "Customerページの手動動作確認";

export const AutoFilledAddress1: ComponentStory<typeof Customer> = () => {
  const mockApi = (axiosMock: MockAdapter) => {
    axiosMock.onGet("/api/v1/address?postcode=1840015").reply(200, {
      address: "東京都XXXXXX",
    });
  };
  return Template(mockApi);
};
AutoFilledAddress1.storyName =
  "郵便番号を入れて、チェックボタンをクリックすると、住所が「住所1」に入ること";
AutoFilledAddress1.play = async ({ canvasElement }: any) => {
  // Arrange
  const expected = "東京都XXXXXX";

  // Act
  const canvas = within(canvasElement);
  const postcodeInputElement = canvas.getByTestId("postcode-input-text");
  userEvent.type(postcodeInputElement, "1840015");
  const addressInputButtonElement = canvas.getByRole("button", {
    name: "住所入力",
  });
  userEvent.click(addressInputButtonElement);

  await waitFor(() => {
    const actual = canvas
      .getByTestId("address1-input-text")
      .getAttribute("value");
    // Assert
    expect(actual).toBe(expected);
  });
};

ハマるポイント

Mock.onで登録したのに、404 not found

プロダクトコードでAPI呼び出し時に使っているaxios instanceをモックしているか気をつけよう。
例えば、このサンプルコードでaxiosInstanceを作って、API呼び出しをしているのであれば、const axiosMock = new MockAdapter(axiosInstance);MockAdapterを作る時もそのinstanceを引数としていれなければならない。const axiosMock = new MockAdapter(axios);にしてしまうと、Mockされず、404 not foundになってしまう。
axios-instance.ts

import axios from "axios";

export const axiosInstance = axios.create();

address.ts

import { axiosInstance } from "./axios-instance";

export default class Address {

  /**
   * BFF の GET api/v1/address APIを呼び出すメソッド
   */
  async getAddress(postcode: string): Promise<AddressResponse> {
    const response = await axiosInstance.get(`/api/v1/address?postcode=${postcode}`);
    return response.data;
  }
}

type AddressResponse = {
  address: string;
};
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0