こんにちは、「PR TIMES Webクリッピング」の開発リーダーをしている小張です。
Storybookをユニットテストで活用している取り組みについて、紹介したいと思います。
テスト文化と現状の課題
半年ほど前から社内にテストを書く文化が根づき始め、フロントエンドのユニットテストも増えてきました。

それに伴い他のメンバーが書いたテストを修正する機会が増えたのですが、修正が難しい場合には一時的にtest.todoとしたり、テストを書いた人に修正を依頼するなどの現象が発生していました。
テストの修正が難しい一因として、testing-libraryが行った画面操作を視覚で確認できないことがありました。そこで昨年導入したStorybookを使って、動作を画面で確認しながらテストを書いていく取り組みをはじめました。
Storybook導入当初の目的についてはこちらに詳しくまとめています。

Storybookの活用
play function
Storybookには play function というinteractionをテストするための機能が備わっており、この関数にユーザーの操作を書くことで動作の確認ができます。
下記のようなStoryを作成して、play functionにテストしたい動作を記述します。
export const UncheckAll = {
name: '全てのチェックを外せること',
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// 全てのチェックを外す
await userEvent.click(
canvas.getByRole('checkbox', { name: 'パブリシティ' }),
);
await userEvent.click(
canvas.getByRole('checkbox', { name: 'パブリシティ転載' }),
);
await userEvent.click(
canvas.getByRole('checkbox', { name: 'リリース原文転載' }),
);
},
} satisfies Story;
このStoryを表示するとInteractionsタブでplay functionの動作を確認できます!

さらにplay functionはUnitテストでも実行することができます。ここではPR TIMESでテストランナーとして採用しているvitestを使います。
具体的には、下記のようにcomposeStoriesで上記のStoryをテストファイルに読み込み、play functionの実行とアサーションを行います。
const { UncheckAll } = composeStories(stories);
it('全てのチェックを外せること', async () => {
const { container, getByRole } = render(<UncheckAll />);
await UncheckAll.play({ canvasElement: container });
expect(getByRole('checkbox', { name: 'パブリシティ' })).not.toBeChe
expect(
getByRole('checkbox', { name: 'パブリシティ転載' }),
).not.toBeChecked();
expect(
getByRole('checkbox', { name: 'リリース原文転載' }),
).not.toBeChecked();
});
より詳細な書き方については以下の記事が非常に参考になります。
test-runnerとの比較
上記ではvitestの実行環境でテストを回していますが、Storybook公式からも @storybook/test-runner というjestベースの実行環境が提供されています。@storybook/jest と組み合わせることでアサーションまでplay function上で完結することが出来ます。
mock回りのサポートが弱いので採用は見送っていますが、今後のアップデートでmock関係の機能追加が入る可能性もあるようなので、注視していきたいです。
We have a long list of quality of life improvements here that we’ll be rolling out in 7.x, especially around better mocking, full page testing, and compatibility.
https://storybook.js.org/blog/storybook-7-0/
デプロイ環境
当初mainブランチに対してGitHub Actionsを構築し、自動でStorybookがデプロイされる運用を行っていました。さらに現在ではPull RequestごとにStorybookのプレビュー環境を作成し、こちらもpushされたタイミングで自動デプロイが行われるようになっています。
デプロイが完了すると、Pull RequestにURLがコメントされるようになっています。

Storybookをテストで活用してみて
普段のプロジェクトでStorybookが書かれるようになった
Story fileの数が10 files(2023年1月)から、168 files(2023年4月)に増加しました。
これは今までデザインシステムに対してしか書かれていなかったStorybookが、プロジェクトごとのコンポーネントに対しても書かれるようになったためです。
単純な数だけでなく、Pull Requestに実装した箇所のStoryリンクを貼ったり、プロジェクトの進捗報告に使うなど、エンジニアを中心にStorybookを活用する場面が増えてきたと感じています。
コンポーネント駆動開発について
前述と関連するのですが、Storybookによってコンポーネント単位で進捗が把握できるようになりました。
コンポーネント実装を1チケットとして切りやすくなり、ご覧のようにPull Requestの単位がわかりやすくなっています。また、Storybookと一緒にテストも書かれることも増え、コンポーネント単位のUnitテストも増加しつつあります。

まとめ
play functionを使って、テストが可視化されデバッグが容易になりました。またStorybookを活用してコンポーネントにフォーカスした開発も行う動きが出てきました。
PR TIMESではフロントエンドはもちろんのこと様々な改善活動が行われているので、もしご興味ある方は、ぜひカジュアル面談でお話しできると嬉しいです。