グローバルナビゲーションへ

本文へ

フッターへ

お役立ち情報Blog



Storybook の「play 関数+ composeStories」を使用したテストで、MSW のハンドラを通らないケースの原因について

今回は Storybook に play 関数を定義して API コールをしている Story があり、その Story を composeStories 関数で再利用したテストで、時折テストが失敗する不安定なテストの原因とその対策についてです。

Storybook と MSW のバージョン
  • Storybook v7(2024-06-05 時点で最新のメジャーバージョンは v8)
  • MSW v1(2024-06-05 時点で最新のメジャーバージョンは v2)

概要

Story 例)

// Foo.stories.tsx
const Uploading: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    const user = userEvent.setup({ delay: null });
    await user.upload(
      canvas.getByTestId("upload"),
      new File["dummy"](),
      "sample.jpg",
      { type: "image/jpg" }
    );
  },
  parameters: {
    msw: {
      handlers: [
        rest.post("https://example.com/files", (_, res, ctx) => {
          return res(ctx.delay("infinite"), ctx.status(200));
        }),
      ],
    },
  },
};

テスト例)

// Foo.test.tsx
import * as stories from "./Foo.stories";

const { Uploading } = composeStories(stories);

test("ファイルアップロード中はボタンがdisabledであること", async () => {
  server.use(...Uploading.parameters["msw"].handlers);
  const { container } = render(<Uploading />);

  await Uploading.play(container);

  expect(screen.getByRole("button", { name: "アップロード" })).toBeDisabled();
});

composeStories を使用して Story を再利用するテストでは、MSW ハンドラの登録はされないので、忘れずにテストコード上で MSW の setupServer 関数を使用して作成した server.use() を使用しているにも関わらず、時折 MSW の resolver を通らずにテストが失敗するといった状態です。

現象は時折発生していて、resolver を通る時もあれば通らない時もあるといった不安定な状態。

原因

  // Foo.stories.tsx
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    const user = userEvent.setup({ delay: null }); // ここの `{ delay: null }`
    await user.upload(canvas.getByTestId("upload"), new File["dummy"], "sample.jpg", { type: "image/jpg" });
  },

Testing Library(Storybook の userEvent が内部的に使用)の userEvent.type は多量の文字数入力のエミュレートで実行時間が伸びるといった問題があり、その対策は以下の方法があります。

対策方法

  1. userEvent.typeuserEvent.paste に変える
  2. userEventfireEvent に変える
  3. userEvent.setup の引数に { delay: null } を追加する

今回のケースでは「3. userEvent.setup の引数に { delay: null } を追加する」を採用していました。

その結果、ユーザ操作のエミュレート時に Testing Library がよしなに wait していた処理をスキップする状態となっており、テスト実行時の状況によっては MSW の resolver を通ったり通らなかったりといった状態になっていたようです。

対策

引数のオブジェクトから delay をトル

  // Foo.stories.tsx
  const Uploading: Story = {
    play: async ({ canvasElement }) => {
      const canvas = within(canvasElement);
-     const user = userEvent.setup({ delay: null });
+     const user = userEvent.setup();
      await user.upload(canvas.getByTestId("upload"), new File["dummy"], "sample.jpg", { type: "image/jpg" });
    },
    parameters: {
      msw: {
        handlers: [
          rest.post("https://example.com/files", (_, res, ctx) => {
            return res(ctx.delay("infinite"), ctx.status(200));
          })
        ]
      }
    }
  }

さいごに

時折失敗するテストは原因を特定するのが難しく、特定までに時間が掛かるのがつらいところです。

似たような不安定なテストを抱えている方は、userEvent.setup の引数に { delay: null } が指定されていないか確認してみてください。

この記事を書いた人

美髭公
美髭公事業開発部 web application engineer
2013年にアーティスに入社。システムエンジニアとしてアーティスCMSを使用したWebサイトや受託システムの構築・保守に携わる。環境構築が好き。
この記事のカテゴリ

FOLLOW US

最新の情報をお届けします