🧪

StorybookでもVitestのMockが使えるようになりました

に公開

概要

Storybookでは外部APIとのやり取りなどを行う複雑なモジュールをモックする仕組は存在していたものの、セットアップが複雑であったために導入を諦めた方はいらっしゃると思います。

VitestMockモジュールさえ使えれば。。!

そんなことを思ったことはありませんか?実はStorybookにて最近導入されたsb.mock()を利用することで、VitestMockを利用できるようになったのです。

参考リポジトリ

https://github.com/HirotoShioi/Storybook-mocking-with-vitest

導入方法

ここではTodoリストを取得するフックをtodos/queries.tsモックする方法を紹介します

todos/queries.tsは以下のようになっています

import { useQuery } from "@tanstack/react-query";
import { fetchTodos } from "./api";

export function useGetTodos() {
    return useQuery({
        queryKey: ["todos"],
        queryFn: fetchTodos, // 外部APIを叩いている
    });
}

preview.tsファイルにてモックしたいファイルを指定

.storybook/preview.tsxにてモックしたいモジュールのファイルを指定します

import type { Preview } from '@storybook/react-vite'
import { sb } from 'storybook/test'

sb.mock(import('../src/features/todos/queries.ts'))

モックを利用しているコンポーネントのstoryを実装

todos/queries.tsを利用しているコンポーネントのstoryを実装します。sb.mock()によってuseGetTodosをモック可能になったため、以下のような実装が可能になります。

import type { Meta, StoryObj } from '@storybook/react-vite';
import { TodoList } from './TodoList';
import { useGetTodos } from '../features/todos/queries';
import type { Mock } from 'vitest';

const meta: Meta<typeof TodoList> = {
  title: 'Components/TodoList',
  component: TodoList,
};

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

export const Default: Story = {
  beforeEach: () => {
    (useGetTodos as unknown as Mock).mockReturnValue({
      isLoading: false,
      isError: false,
      data: [
        { id: 1, title: 'Learn Storybook', completed: false },
        { id: 2, title: 'Build a Todo App', completed: true },
        { id: 3, title: 'Master Tailwind CSS', completed: false },
      ],
    });
  },
};

Interaction Testも実施可能

モックされたモジュールを用いてレンダリングされたコンポーネントのInteraction Testも可能です。以下はisErrorがtrueの時にエラー状態になっていることを確認するテストです。

export const Error: Story = {
  beforeEach: () => {
    useGetTodosMock.mockReturnValue({
      isLoading: false,
      isError: true,
      data: undefined,
    });
  },
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    await expect(canvas.getByText('Error loading todos. Please try again later.')).toBeInTheDocument();
  },
};

また上記テストをGithub Actions上でも実行可能です。
https://github.com/HirotoShioi/Storybook-mocking-with-vitest/actions/runs/19518987756/job/55877953510

参考資料

Discussion