Storybook(v7)をVitestで再利用するとき
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/
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()
のように呼ぶことができます。
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が推奨されています。
参照:
注意点その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での設定
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の設定を読み込ませる必要があります(ここで結構詰まった。。。。)。
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のコンフィグファイルに追記します。
/// <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