🧪

Storybook(v7)をVitestで再利用するとき

2023/07/01に公開

Storybookのstoryは、Vitestなどにimportして、テストで再利用することできます。
その際にエラーや手間取ったことがあったので共有できればと思います。

環境(一部省略)

react: 18.2.0
react-dom: 18.2.0
vite: 4.3.9
typescript: 5.0.2
@storybook/react: 7.0.22
@storybook/react-vite: 7.0.22
vitest: 0.32.2
@chakra-ui/react: 2.7.1
recoil: 0.7.7
react-router-dom: 6.13.0

storyの作成

CSF3形式でstoryを作成していきます。
CSFとは、Component Story Formatの略で、storyのフォーマットのことを指します。

CSF3の主な特徴は以下のとおりです。

  • storyがObjectになった
  • metaのタイトルが必須でなくなった
  • Play functionでインタラクティブストーリーが作成できるようになった

公式:https://storybook.js.org/blog/storybook-csf3-is-here/

src/components/HogeComponent.stories.tsx
import type { Meta, StoryObj } from "@storybook/react";
import { HogeComponent } from ".";

const meta: Meta<typeof HogeComponent> = {
  component: HogeComponent
};

export default meta;
type Story = StoryObj<typeof HogeComponent>;

export const HogeComponent: Story = {
  render: args => <HogeComponent {...args} />
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    const link = canvas.getByRole("Link");

    await userEvent.click(link);
  }
};

HogeComponent.args = {
// 省略
};

Testファイルの作成とStoryのインポート

テストファイルを作成します。通常のコンポーネントテストは、コンポーネントを直接インポートしますが。今回は@storybook/reactからcomposeStories、作成したstoryファイルをstoriesとしてimportすることで、作成したStoryをVitestで再利用することができます。
Story内でplay関数を使ってイベントを記述している場合は、そちらも .play()のように呼ぶことができます。

scr/component/index.test.tsx
import { composeStories } from "@storybook/react";
import * as stories from "./HogeComponent.stories";
import { render, screen } from "@testing-library/react";

const { Default } = composeStories(stories);

describe("HogeComponent", () => {
  describe("hogehoge", async () => {
    test("hogehoge", async () => {
      const { container } = render(<Default />);
      const link = await screen.findByRole("Link");

           await Default.play({ canvasElement: container });  // story上で記述したplay関数の実行
	   
      // expectでテスト
    });
  });
});

注意点その1: Storybook7の場合は@storybook/reactを使うべし

StoryをVitest(or Jest)に追加するために色々調べていると、過去の記事では@storybook/testing-reactのcomposeStoriesという関数を使う、とあるのですが、Storybook7の場合は、@storybook/reactからimportします。
@storybook/testing-reactは将来的に非推奨になるそうで、公式でも@storybook/reactへのmigrateが推奨されています。
参照:
https://storybook.js.org/addons/@storybook/testing-react

注意点その2: いろんなライブラリーと使うとき

純粋なreactのみのコンポーネントであれば上記までステップで問題ないのですが、大体のアプリはUIライブリー、状態管理、ルーティング、などなどたくさんライブラリーを利用しています。
その場合、storybook上で表示させるには storybookのaddsOnやdecoratorsを使ってmockしてあげる必要があります。
Chakra UI, react-router-dom, recoilを使ったときに必要だった設定を紹介します。

@chakra-ui/storybook-addonの追加

yarn add -D @chakra-ui/storybook-addon

.storybook/preview.tsxでの設定

.storybook/preview.tsx
import type { Preview } from "@storybook/react";
import { theme } from "../src/styles/theme";
import { MemoryRouter } from "react-router-dom";
import { RecoilRoot } from "recoil";
import React from "react";

const BaseDecorator = Story => (
  <MemoryRouter>
    <RecoilRoot>
      <Story />
    </RecoilRoot>
  </MemoryRouter>
);

export const decorators = [BaseDecorator];

const preview: Preview = {
  decorators: decorators,
  parameters: {
    chakra: { theme }, // カスタムthemeの読み込み
  }
};

export default preview;

vitest.setup.tsでの設定

Storybook側でのmockは、previewで記述したdecoratorsで作成されますが、VitestからStoryを読み込んでテストを走らせるときに、このmockは読み込まれないみたいで、別途 Vitestのセットアップファイルにstorybookの設定を読み込ませる必要があります(ここで結構詰まった。。。。)。

vitest.setup.ts
import "@testing-library/jest-dom";
import { setProjectAnnotations } from "@storybook/react";
import * as globalStorybookConfig from "./.storybook/preview";

setProjectAnnotations(globalStorybookConfig);

vite.config.tsの設定

vitest.setup.tsに設定したファイルを読み込むようにviteのコンフィグファイルに追記します。

vite.config.ts
/// <reference types="vitest" />
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import istanbul from "vite-plugin-istanbul";

export default defineConfig({
  resolve: {
    alias: [{ find: "@", replacement: "/src" }]
  },
 // 省略
  test: {
    setupFiles: "./vitest.setup.ts" // ここ
  }
});

あとは、yarn testやら設定したコマンドでテストを走らせれば完了!
StorybookとVitestそれぞれのMockの設定方法を探すのに時間取られた〜😭

Discussion