Storybook 8.3 で導入された Vitest 対応を React と Next.js で試す
Storybook 8.3 のリリーつについて
先日 Storybook 8.3 がリリースされました。
このリリースでの目玉機能は、なんといっても、待望の Vitest 対応ではないでしょうか。
以下は、7月末に一部公開されていたスクリーンキャスト。
とはいえ、何故か大々的に告知されていなかったり、Changelog には以下のようにあるのですが
⚡️ First-class Vitest integration to run stories as component tests
🔼 Next.js-Vite framework for Vitest compatibility and better DX
🗜️ Further reduced bundle size for a smaller install footprint
🌐 Experimental Story globals to standardize stories for themes, viewports, and locales
💯 Hundreds more improvements
上記の内、Vitest に関する以下2つについては、実はまだ Experimental だったりと...
⚡️ First-class Vitest integration to run stories as component tests
🔼 Next.js-Vite framework for Vitest compatibility and better DX
不思議な?(雑な?)リリースがされております。
とはいえ、Storybook の Story が手軽に Vitest で実行できることになったのは大変喜ばしいことなので、この記事ではドキュメントを参照しつつ、React と Next.js におけるセットアップから、テストの実行までを紹介したいと思います。
追記: 2024/09/25
後日 (2024/09/24) に、公式の X と Blog にて以下の告知と、記事が公開されました。
Storybook Vitest Plugin について
Vitest で Storybook の Story を実行するための Plugin になります。
パッケージとしては @storybook/experimental-addon-test
と、addon として配布されています。
React で試す
以降で紹介するセットアップを適用し、作成したコードは以下になります。
以下、順序を追ってその内容を見ていきたいと思います。
検証用プロジェクトの用意
以下のように、適当なディレクトリを作成し、storybook@latest init
コマンドを実行し、
※ 以降、pnpm
をご利用の場合は npx
を pnpx
に、npm
を pnpm
に置き換えてお試しください。
mkdir demo_storybook-react-vitest
cd demo_storybook-react-vitest
npx storybook@latest init
storybook init
を空のディレクトリで実行すると、雛形に基づいたコードを生成してくれるので、聞かれた質問には以下の様に回答してください。
-
Choose a project template
->React + Vite (TS)
これで、vite で bundle を行う React App の Storybook を用いたプロジェクトの雛形が完成します。
Storybook Vitest Plugin のセットアップ
addon 経由のセットアップ
以下のコマンドを実行する事で、@storybook/experimental-addon-test
経由で、必要な package のインストールと、必要なファイルのセットアップを自動で行う事ができます。
npx storybook add @storybook/experimental-addon-test
セットアップコマンドで作成されたファイルの内容
上記のセットアップが環境すると、以下のファイルの変更と追加が行われます。
変更されたファイル
package.json
に以下のモジュールが追加される
vite
@vitest/browser
@storybook/experimental-addon-test
内容から、Storybook Vite Plugin は Vite の Browser Mode に対応しており、利用することを推奨していることが分かります。
また、ドキュメントにもその様に記載があります。
追加されたファイル
vitest.workspace.ts
このファイルは、 Vitest の workspace 機能を利用するたもえのもので、1つのプロジェクト内で複数の vitest の設定を定義し、使い分けることを可能にするものです。
import { defineWorkspace } from 'vitest/config';
import { storybookTest } from '@storybook/experimental-addon-test/vitest-plugin';
// More info at: https://storybook.js.org/docs/writing-tests/vitest-plugin
export default defineWorkspace([
'vite.config.ts',
{
extends: 'vite.config.ts',
plugins: [
// See options at: https://storybook.js.org/docs/writing-tests/vitest-plugin#storybooktest
storybookTest(),
],
test: {
name: 'storybook',
browser: {
enabled: true,
headless: true,
name: 'chromium',
provider: 'playwright',
},
// Make sure to adjust this pattern to match your stories files.
include: ['**/*.stories.?(m)[jt]s?(x)'],
setupFiles: ['./.storybook/vitest.setup.ts'],
},
},
]);
defineWorkspace
には、配列で「Vitest の config オブジェクト」あるいは、「Vitest の config のパス」を指定する事ができ、ここでは、プロジェクトルートの vite.config.ts
を Story 以外の Unit テストの config として見立て、それとは別に test.name
で storybook
と命名した 「*.stories.*
ファイルのみをテストする」 config を定義しているようです。これにより、 npm exec vitest --project=storybook
で、stories
のみを対象とした Vitest を実行できるようになります。
また、config の内容からも Browser Mode で実行するように設定されていることが分かります。
.storybook/vitest.setup.ts
vitest 実行時 Storybook の preview.ts
と同じ環境をつくるための設定
import { beforeAll } from "vitest";
import { setProjectAnnotations } from "@storybook/react";
import * as projectAnnotations from "./preview";
// This is an important step to apply the right configuration when testing your stories.
// More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations
const project = setProjectAnnotations([projectAnnotations]);
beforeAll(project.beforeAll);
Vitest の実行を検証
Browser Mode で headeless
セットアップ完了後、以下のコマンドで vitest を実行すると、 Storybook の Story が Vitest の Browser Mode で実行されます。
npm exec vitest
結果は、以下のように Vitest の watch mode での実行結果が表示され、各種 Vitest のキー操作ができます。
Browser Mode を headless にせず実行する
headless を無効化する事で、borwser.name
で指定しているブラウザで http://localhost:5173/#/
を開いた状態で起動し、以下のような画面が表示され、ブラウザ上で実行結果を確認する事ができます。
vitest.workspace.ts
の修正
Vitest の Browser Mode を headless: false
に修正する
修正後の `vitest.workspace.ts`
import { storybookTest } from "@storybook/experimental-addon-test/vitest-plugin";
import { defineWorkspace } from "vitest/config";
// More info at: https://storybook.js.org/docs/writing-tests/vitest-plugin
export default defineWorkspace([
"vite.config.ts",
{
extends: "vite.config.ts",
plugins: [
// See options at: https://storybook.js.org/docs/writing-tests/vitest-plugin#storybooktest
storybookTest(),
],
test: {
name: "storybook",
browser: {
enabled: true,
headless: false,
name: "chromium",
provider: "playwright",
},
// Make sure to adjust this pattern to match your stories files.
include: ["**/*.stories.?(m)[jt]s?(x)"],
setupFiles: ["./.storybook/vitest.setup.ts"],
},
},
]);
Brower Mode を利用せず実行する
Browser Mode を使った方法については、先の結果でわかったと思います。
しかし、以下の理由で .stories.*
ファイルをそのまま、vitest で jsdom
や happy-dom
を使って簡単にテストの実行を行いたいという考えの方も多いはず。
弊社でも以下の記事の手法を用いてコンポーネントテストを実行するケースがいくつかあります。
というわけで、ここでは vitest.workspace.ts
の設定を変更し、Browser Mode の実行を無効化した上で happy-dom
を利用した方法を試してみます。
必要なパッケージの追加
以下のコマンドで利用する happy-dom
を追加します。
npm i -D happy-dom
vitest.workspace.ts
の変更
test.browser.enabled
に false
を指定あるいは、test.browser
を削除し、 test.environment
に happy-dom
を指定します。
修正後の `vitest.workspace.ts`
import { defineWorkspace } from 'vitest/config';
import { storybookTest } from '@storybook/experimental-addon-test/vitest-plugin';
// More info at: https://storybook.js.org/docs/writing-tests/vitest-plugin
export default defineWorkspace([
'vite.config.ts',
{
extends: 'vite.config.ts',
plugins: [
// See options at: https://storybook.js.org/docs/writing-tests/vitest-plugin#storybooktest
storybookTest(),
],
test: {
name: 'storybook',
browser: {
enabled: false,
headless: true,
name: "chromium",
provider: "playwright",
}
environment: 'happy-dom',
include: ['**/*.stories.?(m)[jt]s?(x)'],
setupFiles: ['./.storybook/vitest.setup.ts'],
},
},
]);
テストの実行
npm exec vitest
結果
Browser Mode 利用時に表示されていた Browser runner started by playwright at ...
が表示されていない事がわかります。
Next.js で試す
今Next.js で試すNext.js で試す回、新たに @storybook/experimental-nextjs-vite(と、内部で利用されている vite-plugin-storybook-nextjs )がリリースされており、これらを利用することで、Next.js 向けに実装されたコンポーネントの Story についても、Vitest で実行できるようになっています。
以降で紹介するセットアップを適用し、作成したコードは以下になります。
検証用のプロジェクトを用意
Next.js のプロジェクトを作成
npx create-next-app@latest
質問に対しては、以下の様に回答します。
-
What is your project named?
=>demo_storybook-next-vitest
-
Would you like to use TypeScript?
=>Yes
-
Would you like to use ESLint?
=>No
-
Would you like to use Tailwind CSS?
=>Yes
-
Would you like to use src/ directory?
=>Yes
-
Would you like to use App Router? (recommended)
=>Yes
-
Would you like to customize the default import alias (@/*)?
=>Yes
-
What import alias would you like configured?
=>@/*
Storybook を追加
以下のコマンドで、storybook を追加し @storybook/nextjs
を利用した環境をつくります。
cd demo_storybook-next-vitest
npx storybook@latest init
Storybook Vitest Plugin のセットアップ
以下のコマンドで、先に紹介した @storybook/experimental-nextjs-vite
と vite-plugin-storybook-nextjs
を追加した状態を作ります。
npx storybook@latest add @storybook/experimental-addon-test
変更されたファイル
package.json
以下のパッケージが追加されます。
vite
vitest
playwright
@vitest/browser
@storybook/experimental-nextjs-vite
@storybook/experimental-addon-test
追加されたファイル
vitest.config.ts
import { defineConfig } from 'vitest/config';
import { storybookTest } from '@storybook/experimental-addon-test/vitest-plugin';
import { storybookNextJsPlugin } from '@storybook/experimental-nextjs-vite/vite-plugin';
// More info at: https://storybook.js.org/docs/writing-tests/vitest-plugin
export default defineConfig({
plugins: [
// See options at: https://storybook.js.org/docs/writing-tests/vitest-plugin#storybooktest
storybookTest(),
// More info at: https://github.com/storybookjs/vite-plugin-storybook-nextjs
storybookNextJsPlugin(),
],
test: {
name: 'storybook',
browser: {
enabled: true,
headless: true,
name: 'chromium',
provider: 'playwright',
},
// Make sure to adjust this pattern to match your stories files.
include: ['**/*.stories.?(m)[jt]s?(x)'],
setupFiles: ['./.storybook/vitest.setup.ts'],
},
});
.storybook/vitest.setup.ts
import { beforeAll } from 'vitest';
import { setProjectAnnotations } from '@storybook/nextjs';
import * as projectAnnotations from './preview';
// This is an important step to apply the right configuration when testing your stories.
// More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations
const project = setProjectAnnotations([projectAnnotations]);
beforeAll(project.beforeAll);
Vitest の実行を検証
npm exec vitest
実行すると、以下のように、 sytled-jsx/style
の import が解決できないと怒られます。
[vite] Internal server error: Failed to resolve import "styled-jsx/style" from "src/stories/Button.tsx". Does the file exist?
storybook 追加時に生成された Component が styled-jsx
に依存したものになっているからですね。
styled-jsx
を追加して際実行
まず、styled-jsx
を追加します
npm i -S styled-jsx
その後、以下のコマンドで再度 Vitest を実行します。
npm exec vitest
結果
![vitest の Browser Mode で Storybook の Story を実行した結果](/images/storybook-8-3-vitest/2024-09-14 17-storybook-nextjs-vitest-success.png)
ちゃんと全て成功している事が分かります。
Next.js の API を利用したコンポーネントの Story を検証する
しかし、storybook init
で追加された Component はどれも、Client Component かつ、next/headers
等に依存していないため、次は、Next.js の cookies
や RSC を利用しているコンポーネントの Story の実行を検証してみます。
手っ取り早く、検証に使えそうな vite-plugin-storybook-nextjs
の example
から next/headers
と use server
を利用しているコード app/components/Header
以下にを持ってきて配置します。
上記のコードを配置した後、再度 npm exec vitest
を実行すると、以下のエラーメッセージが表示され、Header.stories.tsx
のみ失敗します。
async/await is not yet supported in Client Components, only Server Components. This error is often caused by accidentally adding `'use client'` to a module that was originally written for the server.
Storybook で RSC に関する設定をして実行
上記の問題に対処する方法は、いまのところドキュメントには記載がないため、vitest-plugin-storybook-nextjs
の example/.storybook
内の設定を確認します。
すると、以下の @storybook/react
から rsc
を有効にしていると思われる preview
の import がみつかります。
こちらのファイルの内容を見てみると、以下のようになっており、
.storybook/vitest.setup.ts
の setProjectAnnotations に { parameters: { react: { rsc: true } } }
を追加すればよいことがわかります。
修正後の `.storybook/vitest.setup.ts`
import { setProjectAnnotations } from "@storybook/nextjs";
import { beforeAll } from "vitest";
import * as projectAnnotations from "./preview";
// This is an important step to apply the right configuration when testing your stories.
// More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations
const project = setProjectAnnotations([
projectAnnotations,
{
parameters: {
react: {
rsc: true,
},
},
},
]);
beforeAll(project.beforeAll);
修正した上で、再度実行した結果が以下です。
Browser Mode を利用しない形で実行
こちらも、Browser Mode を利用せずに実行できるの確認したいと思います。
vitest-plugin-storybook-nextjs
の README をみると、以下のように、RSC を利用したい場合は、jsdom
を追加し、設定する必要があると記載があります。
When testing components that rely on Next.js Server Actions, you need to ensure that your story files are set up to use the jsdom environment in Vitest. This can be done in two ways:When testing components that rely on Next.js Server Actions, you need to ensure that your story files are set up to use the jsdom environment in Vitest. This can be done in two ways:
https://github.com/storybookjs/vite-plugin-storybook-nextjs#nextjs-server-actions
試しに、happy-dom
で試したところ、以下のエラーが発生し、Server Action を利用している Story のテストが失敗しました。
Error: Cannot find package 'jsdom' imported from ...
というわけで、以下のコマンドで jsdom
を追加し、
npm i -D jsdom
vitest.config.ts
を修正した上で、
修正後の `vitest.config.ts`
import { storybookTest } from "@storybook/experimental-addon-test/vitest-plugin";
import { storybookNextJsPlugin } from "@storybook/experimental-nextjs-vite/vite-plugin";
import { defineConfig } from "vitest/config";
// More info at: https://storybook.js.org/docs/writing-tests/vitest-plugin
export default defineConfig({
plugins: [
// See options at: https://storybook.js.org/docs/writing-tests/vitest-plugin#storybooktest
storybookTest(),
// More info at: https://github.com/storybookjs/vite-plugin-storybook-nextjs
storybookNextJsPlugin(),
],
test: {
name: "storybook",
browser: {
// Browser Mode を無効化
enabled: false,
headless: true,
name: "chromium",
provider: "playwright",
},
// jsdom を利用するように指定
environment: "jsdom",
// Make sure to adjust this pattern to match your stories files.
include: ["**/*.stories.?(m)[jt]s?(x)"],
setupFiles: ["./.storybook/vitest.setup.ts"],
},
});
再度実行してみます。
npm exec vitest
結果は、以下のように playwright を起動せずに実行されます。
細かい部分は触れませんが、next/navigation
などの module についても mock した上でテストを実行できるはずです。
Storybook での Next.js 関連ファイルのビルドも Vite で行うようにする
ここまでで、作成した内容でnpm run storybook
を実行すると、以下のように webpack で実行されている事がわかります。
@storybook/core v8.3.0
info => Starting manager..
info => Starting preview..
info Addon-docs: using MDX3
info => Using implicit CSS loaders
info => Using SWC as compiler
info => Using default Webpack5 setup
Next.js のコードを Vitest で実行できるようになったので、Storybook についても同じ様に Vitest 経由で実行するようにしてみましょう。
@storybook/experimental-nextjs-vite
を利用する
Storybook の Next.js 向けのドキュメントをみると、With Vite
という Vite を利用するためのドキュメントが追加されています。
上記にあるように、.storybook/main.ts
の framework
を @storybook/experimental-nextjs-vite
に修正します。
修正後の `.storybook/main.ts`
import type { StorybookConfig } from "@storybook/nextjs";
const config: StorybookConfig = {
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
addons: [
"@storybook/addon-onboarding",
"@storybook/addon-links",
"@storybook/addon-essentials",
"@chromatic-com/storybook",
"@storybook/addon-interactions",
"@storybook/experimental-addon-test",
],
framework: {
// vite で build を行うようにする
name: "@storybook/experimental-nextjs-vite",
options: {},
},
};
export default config;
これで、npm run storybook
や npm run build-storybook
も Vite で実行され、これまでよりも高速に build できる様になります。
感想
React については、ドキュメントの Auto Setup を参考にセットアップすれば、わりと素直に実行できることがわかりました。
(※ 今回は試していませんが、おそらく vue 等も同じ様に設定できるのではないでしょうか。)
また、Next.js については、まだドキュメントの整備ができておらず、vite-plugin-storybook-nextjs の README や example を参照しながら設定を行う必要があるため、注意が必要そうです。(※ vite-plugin-storybook-nextjs の example のコードを確認してみると、8.3 に対応できいなかったりと、こちらも参考にする際に注意が必要です。)
今回は、触れられていませんが、Storybook Vitest Plugin と Next.js の以下の FAQ の内容についても目を通しておく必要がありそうです。
- https://storybook.js.org/docs/writing-tests/vitest-plugin#faq
- https://storybook.js.org/docs/get-started/frameworks/nextjs#faq
以上、導入の参考になれば幸いです。
Discussion