1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Storybook v10 + Vitest v4 + Storybook MCPアドオン活用でテストを簡単に実装する

Posted at

Storybook v10 + Vitest v4 + Storybook MCPアドオン活用でテストを簡単に実装する

社内プロダクトのReactリプレイスを担当しているdendeです。
この記事では最近プロダクトに導入したStorybook v10 + Vitest v4 のテスト運用と、Storybook公式の @storybook/addon-mcp を使った “StorybookをMCPサーバー化してAIエージェントと連携する方法について書きます。

導入することで何ができるか

Storybook × Vitest

  • Storyplay 関数を定義することで、コンポーネントがレンダリングされた後に自動的に実行されるインタラクションを記述でき、Storybookをテストケースとして利用できる
  • ユーザー操作のシミュレーションや、状態の遷移などを高速にテストできる
  • test結果はStorybook上で確認もできる

MCPアドオン

  • Storybook MCP Addonは、AIエージェントがStorybookのコンポーネント情報に直接アクセスして活用できるようにする公式アドオン
  • バージョンアップや、Storybookのベストプラクティスに基づいたStoryファイルを簡単に作成できる

構成(前提)

  • Storybook v10(Viteベースを想定)
  • Vitest v4
  • Storybookのテストは VitestのBrowser Mode + Playwright(Chromium) を利用
  • MCPは @storybook/addon-mcp を利用

1. Storybook v10 + Vitest v4 + Playwright をセットアップ

Storybook(未導入なら)

npm create storybook@latest

実行するvitestのアドオンやPlaywrightのセットアップが対話形式で進められます。

dende@dende-h:~/my-next-app$ npm create storybook@latest

> my-next-app@0.1.0 npx
> create-storybook


┌  Initializing Storybook
│
●  Adding Storybook version 10.1.9 to your project
│
◇  Framework detected: nextjs-vite
│
◇  New to Storybook?
│  Yes: Help me with onboarding
│
●  Storybook collects completely anonymous usage telemetry. We use it to shape
│  Storybook's roadmap and prioritize features. You can learn more, including how
│  to opt out, at https://storybook.js.org/telemetry
│
◆  Storybook configuration generated
│
│  - Configuring ESLint plugin
│  - Configuring main.ts
│  - Configuring preview.ts
│  - Adding Storybook command to package.json
│  - Copying framework templates
│
◇  Adding dependencies to package.json
│
│
◆  Dependencies added to package.json
│
│  Adding devDependencies:
│  - storybook@^10.1.9
│  - @storybook/nextjs-vite@^10.1.9
│  - @chromatic-com/storybook@^4.1.3
│  - @storybook/addon-vitest@^10.1.9
│  - @storybook/addon-a11y@^10.1.9
│  - @storybook/addon-docs@^10.1.9
│  - @storybook/addon-onboarding@^10.1.9
│  - vite@^7.3.0
│  - eslint-plugin-storybook@^10.1.9
│  - vitest
│  - playwright
│  - @vitest/browser-playwright
│  - @vitest/coverage-v8
│
◇  Dependencies installed
│
◇  Configuring addons...
│
│  Configuring @chromatic-com/storybook...
│  @chromatic-com/storybook configured
│  
│  Configuring @storybook/addon-vitest...
│  Creating a Vitest setup file for Storybook:
│  /home/dende/my-next-app/.storybook/vitest.setup.ts
│  Configuring @chromatic-com/storybook...
│  @chromatic-com/storybook configured
│  
│  Configuring @storybook/addon-vitest...
│  Creating a Vitest setup file for Storybook:
│  /home/dende/my-next-app/.storybook/vitest.setup.ts
│  
│  Creating a Vitest config file:
│  Configuring @chromatic-com/storybook...
│  @chromatic-com/storybook configured
│  
│  Configuring @storybook/addon-vitest...
│  Creating a Vitest setup file for Storybook:
│  /home/dende/my-next-app/.storybook/vitest.setup.ts
│  
│  Creating a Vitest config file:
│  /home/dende/my-next-app/vitest.config.ts
│  Configuring @chromatic-com/storybook...
│  @chromatic-com/storybook configured
│  
│  Configuring @storybook/addon-vitest...
│  Creating a Vitest setup file for Storybook:
│  /home/dende/my-next-app/.storybook/vitest.setup.ts
│  
│  Creating a Vitest config file:
│  /home/dende/my-next-app/vitest.config.ts
│  Setting up a11y addon for @storybook/addon-vitest
│  a11y addon setup successfully
│  Configuring @chromatic-com/storybook...
│  @chromatic-com/storybook configured
│  
│  Configuring @storybook/addon-vitest...
│  Creating a Vitest setup file for Storybook:
│  /home/dende/my-next-app/.storybook/vitest.setup.ts
│  
│  Creating a Vitest config file:
│  /home/dende/my-next-app/vitest.config.ts
│  Setting up a11y addon for @storybook/addon-vitest
│  a11y addon setup successfully
│  @storybook/addon-vitest setup completed successfully
│  Configuring @chromatic-com/storybook...
│  @chromatic-com/storybook configured
│  
│  Configuring @storybook/addon-vitest...
│  Creating a Vitest setup file for Storybook:
│  /home/dende/my-next-app/.storybook/vitest.setup.ts
│  
│  Creating a Vitest config file:
│  /home/dende/my-next-app/vitest.config.ts
│  Setting up a11y addon for @storybook/addon-vitest
│  a11y addon setup successfully
│  @storybook/addon-vitest setup completed successfully
│  @storybook/addon-vitest is now configured and you're ready to run your tests!
│  Here are a couple of tips to get you started:
│  Configuring @chromatic-com/storybook...
│  @chromatic-com/storybook configured
│  
│  Configuring @storybook/addon-vitest...
│  Creating a Vitest setup file for Storybook:
│  /home/dende/my-next-app/.storybook/vitest.setup.ts
│  
│  Creating a Vitest config file:
│  /home/dende/my-next-app/vitest.config.ts
│  Setting up a11y addon for @storybook/addon-vitest
│  a11y addon setup successfully
│  @storybook/addon-vitest setup completed successfully
│  @storybook/addon-vitest is now configured and you're ready to run your tests!
│  Here are a couple of tips to get you started:
│  
│  Configuring @chromatic-com/storybook...
│  @chromatic-com/storybook configured
│  
│  Configuring @storybook/addon-vitest...
│  Creating a Vitest setup file for Storybook:
│  /home/dende/my-next-app/.storybook/vitest.setup.ts
│  
│  Creating a Vitest config file:
│  /home/dende/my-next-app/vitest.config.ts
│  Setting up a11y addon for @storybook/addon-vitest
│  a11y addon setup successfully
│  @storybook/addon-vitest setup completed successfully
│  @storybook/addon-vitest is now configured and you're ready to run your tests!
│  Here are a couple of tips to get you started:
│  
│  • You can run tests with "npx vitest"
│  • Vitest IDE extension shows all stories as tests in your editor!
│
◆  Addons configured successfully
│
│  ✅ @chromatic-com/storybook
│  ✅ @storybook/addon-vitest
│  ✅ @storybook/addon-a11y
│  ✅ @storybook/addon-docs
│  ✅ @storybook/addon-onboarding
│
│  Playwright browser binaries are necessary for @storybook/addon-vitest. The
│  download can take some time. If you don't want to wait, you can skip the
│  installation and run the following command manually later:
│  npx playwright install chromium --with-deps
│
◇  Do you want to install Playwright with Chromium now?
│  Yes

この導入で、Storybook側の設定とVitestの設定が連携され、Browser Mode(Playwright/Chromium)を使った構成が入りやすくなります。

実行スクリプト例(任意)

{
  "scripts": {
    "storybook": "storybook dev -p 6006",
    "test:storybook": "vitest --run --project=storybook"
  }
}
vitest.config.ts
import path from 'node:path';
import { fileURLToPath } from 'node:url';

import { defineConfig } from 'vitest/config';

import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';

import { playwright } from '@vitest/browser-playwright';

const dirname =
  typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url));

// More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon
export default defineConfig({
  test: {
    projects: [
      {
        extends: true,
        plugins: [
          // The plugin will run tests for the stories defined in your Storybook config
          // See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest
          storybookTest({ configDir: path.join(dirname, '.storybook') }),
        ],
        test: {
          name: 'storybook',
          browser: {
            enabled: true,
            headless: true,
            provider: playwright({}),
            instances: [{ browser: 'chromium' }],
          },
          setupFiles: ['.storybook/vitest.setup.ts'],
        },
      },
    ],
  },
});
c
.storybook/vitest.setup.ts
import { setProjectAnnotations } from '@storybook/nextjs-vite';
import { beforeAll } from 'vitest'
import * as projectAnnotations from './preview';

// このセットアップで Storybook の設定を Vitest (Browser Mode) に適用する
const project = setProjectAnnotations([projectAnnotations])

beforeAll(project.beforeAll)

2. Storybook-MCPを設定する

npm install -D @storybook/addon-mcp
.storybook/main.ts
import type { StorybookConfig } from '@storybook/nextjs-vite';

const config: StorybookConfig = {
  "stories": [
    "../src/**/*.mdx",
    "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"
  ],
  "addons": [
    "@chromatic-com/storybook",
    "@storybook/addon-vitest",
    "@storybook/addon-a11y",
    "@storybook/addon-docs",
    "@storybook/addon-onboarding",
    "@storybook/addon-mcp" // MCP addonを追加する
  ],
  "framework": "@storybook/nextjs-vite",
  "staticDirs": [
    "../public"
  ]
};
export default config;

Cursorで使う場合下記の設定が必要だったので、mcp.jsonを記述

.cursor/mcp.json
{
  "mcpServers": {
    "storybook-mcp": {
      "type": "http",
      "url": "http://localhost:6006/mcp"
    }
  }
}

これでStorybookを立ち上げるとMCPサーバーとして機能します。

スクリーンショット 2025-12-16 234206.png

こんな感じのコンポーンネントに対して以下のようなプロンプトで指示します

Before doing any UI, frontend or React development, ALWAYS call the storybook MCP server to get further instructions.
ContactForm.tsxのStoryをベストプラクティスに沿って実装して

このようにテストを含んだStorybookのコードを一発で出してくれます。

import type { Meta, StoryObj } from '@storybook/nextjs-vite';
import { fn, userEvent, within, expect } from 'storybook/test';

import { ContactForm } from './ContactForm';

const meta = {
  title: 'Example/ContactForm',
  component: ContactForm,
  parameters: {
    layout: 'centered',
  },
  tags: ['autodocs'],
  argTypes: {
    submitting: { control: 'boolean' },
    initialValues: { control: 'object' },
  },
  args: {
    onSubmit: fn(),
  },
} satisfies Meta<typeof ContactForm>;

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

/**
 * デフォルトの空の状態のフォーム
 */
export const Default: Story = {};

/**
 * 初期値が入力されている状態のフォーム
 */
export const WithInitialValues: Story = {
  args: {
    initialValues: {
      name: '山田 太郎',
      email: 'taro@example.com',
      message: 'お問い合わせ内容のサンプルテキストです。',
    },
  },
};

/**
 * 送信中の状態(ボタンが無効化され、「送信中...」と表示)
 */
export const Submitting: Story = {
  args: {
    submitting: true,
    initialValues: {
      name: '山田 太郎',
      email: 'taro@example.com',
      message: 'お問い合わせ内容のサンプルテキストです。',
    },
  },
};

/**
 * バリデーションエラーが表示される状態
 * 各フィールドをタッチするとエラーメッセージが表示される
 */
export const WithValidationErrors: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    // 各フィールドをフォーカスしてブラーすることでtouchedをtrueにする
    const nameInput = canvas.getByLabelText('お名前');
    const emailInput = canvas.getByLabelText('メールアドレス');
    const messageInput = canvas.getByLabelText('お問い合わせ内容');

    // 各フィールドをタッチしてバリデーションエラーを発生させる
    await userEvent.click(nameInput);
    await userEvent.tab(); // 次のフィールドへ移動(blurイベント発生)

    await userEvent.click(emailInput);
    await userEvent.tab();

    await userEvent.click(messageInput);
    await userEvent.tab();

    // エラーメッセージが表示されていることを確認
    await expect(canvas.getByText('お名前は必須です')).toBeInTheDocument();
    await expect(canvas.getByText('メールアドレスは必須です')).toBeInTheDocument();
    await expect(canvas.getByText('お問い合わせ内容は必須です')).toBeInTheDocument();
  },
};

/**
 * 不正なメールアドレス形式でのバリデーションエラー
 */
export const InvalidEmailFormat: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    // 名前とメッセージは正しく入力
    const nameInput = canvas.getByLabelText('お名前');
    const emailInput = canvas.getByLabelText('メールアドレス');
    const messageInput = canvas.getByLabelText('お問い合わせ内容');

    await userEvent.type(nameInput, '山田 太郎');
    await userEvent.type(emailInput, 'invalid-email');
    await userEvent.type(messageInput, 'テストメッセージ');

    // メールフィールドからフォーカスを外してバリデーションをトリガー
    await userEvent.tab();

    // メールアドレス形式エラーが表示されていることを確認
    await expect(
      canvas.getByText('メールアドレスの形式が正しくありません')
    ).toBeInTheDocument();
  },
};

/**
 * 正常にフォームを送信するシナリオ
 */
export const SuccessfulSubmission: Story = {
  args: {
    onSubmit: fn(),
  },
  play: async ({ canvasElement, args }) => {
    const canvas = within(canvasElement);

    // フォームに値を入力
    const nameInput = canvas.getByLabelText('お名前');
    const emailInput = canvas.getByLabelText('メールアドレス');
    const messageInput = canvas.getByLabelText('お問い合わせ内容');

    await userEvent.type(nameInput, '山田 太郎');
    await userEvent.type(emailInput, 'taro@example.com');
    await userEvent.type(messageInput, 'これはテストメッセージです。');

    // 送信ボタンをクリック
    const submitButton = canvas.getByRole('button', { name: '送信' });
    await userEvent.click(submitButton);

    // onSubmitが正しい値で呼ばれたことを確認
    await expect(args.onSubmit).toHaveBeenCalledWith({
      name: '山田 太郎',
      email: 'taro@example.com',
      message: 'これはテストメッセージです。',
    });
  },
};

test実行

dende@dende-h:~/my-next-app$ pnpm run test

> my-next-app@0.1.0 test /home/dende/my-next-app
> vitest --run --project=storybook

│
●  Storybook collects completely anonymous usage telemetry. We use it to shape
│  Storybook's roadmap and prioritize features. You can learn more, including how
│  to opt out, at https://storybook.js.org/telemetry

 RUN  v4.0.16 /home/dende/my-next-app

 ✓  storybook (chromium)  src/stories/Header.stories.ts (2 tests) 135ms
 ✓  storybook (chromium)  src/stories/Page.stories.ts (2 tests) 154ms
 ✓  storybook (chromium)  src/stories/Button.stories.ts (4 tests) 167ms
 ✓  storybook (chromium)  src/stories/ContactForm.stories.ts (6 tests) 712ms

 Test Files  4 passed (4)
      Tests  14 passed (14)
   Start at  01:08:54
   Duration  3.60s (transform 0ms, setup 4.50s, import 435ms, tests 1.17s, environment 0ms)

Storybook上でもこのように確認できます
スクリーンショット 2025-12-17 011638.png

まとめ

  • Storybook v10 + Vitest v4 で Story駆動テスト が軽量に実装できる
  • MCPアドオンまで入れると、Storybookが AIエージェントのためのUIナレッジベーステスト実行基盤 になる
  • まずは小さめのコンポーネントから導入して、画像更新・CI固定化・テスト設計をチームに馴染ませるのがおすすめ

参考リンク

1
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?