dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

StorybookのInteraction Testをようやく試した

社内の10分勉強会駆動でようやく Storybook の Interaction Test についてまとめることができたので、メモ。

Interaction Test とはなにか

Storybook 上でユーザーのインタラクション(ボタンクリックやフォーム入力など)を再現できる機能。
上の GIF のように Storybook でインタラクション結果を確認できるだけでなく、インタラクションを記述したスクリプト中にアサーションを書いてテストとして機能させることもできる。また書いたスクリプトTest Runner により CLI や CI で実行することも可能。

使い方

公式ドキュメントの

に従う。

また公式ブログの

チュートリアル形式で実際に試せるのでわかりやすい。
(上記のチュートリアルを写経したものを https://github.com/zaki-yama-labs/storybook-interaction-test-example に置いてある)

$ yarn add --dev @storybook/testing-library @storybook/jest @storybook/addon-interactions

で必要なパッケージをインストールした後、Story に対し play function と呼ばれる関数を記述する。

冒頭の GIF に相当する play function は以下。

import React from "react";
import { within, findByRole, userEvent } from "@storybook/testing-library";
import { expect } from "@storybook/jest";
import { InboxScreen } from "./InboxScreen";

export default {
  component: InboxScreen,
  title: "InboxScreen",
};

// ...中略...

export const EditTask = Template.bind({});
EditTask.parameters = { ...Default.parameters };
EditTask.play = async ({ canvasElement }) => {
  const canvas = within(canvasElement);
  const getTask = (name) => canvas.findByRole("listitem", { name });

  const itemToEdit = await getTask("Fix bug in input error state");
  const taskInput = await findByRole(itemToEdit, "textbox");

  await userEvent.type(taskInput, " and disabled state", { delay: 300 });
  await expect(taskInput.value).toBe(
    "Fix bug in input error state and disabled state"
  );
};

https://github.com/zaki-yama-labs/storybook-interaction-test-example/blob/e116e542b1ba652afd4345c1ee8d6f279b39e965/src/InboxScreen.stories.js#L69-L82

react-testing-library でのテストを書いたことがある人からすると、基本的な書き方は大きく変わらないのがわかる。ただ import しているライブラリは @storybook/xxx になっている。

この play function を書いた状態で該当の Story を開くと、下部のペインに Interactions というタブが追加される。インタラクションは Story を開いたときに自動的に開始されるが、 Interactions タブ内の ◀️ ▶️ をクリックするとコマ送りで確認することもできる。

CLI から実行する

@storybook/test-runner をインストールすると test-storybook というコマンドが使えるようになるので、これを CLI から実行する。このとき、

  • play function が定義されていない Story に対しては:Story がエラーなくレンダリングされるかどうかを検証する
  • play function が定義されている Story に対しては:play function がエラーなく実行され、すべてのアサーションが pass するかどうかを検証する

という挙動になる。

Interaction Test で何がうれしいの?

ユースケースを模したテストを目視で確認しながら書けるのでわかりやすいですね、ということに加え、 VRT(Visual Regression Testing)と組み合わせるとよりおいしい んだろうなと思った。

VRT(Visual Regression Testing)

  • UIを画像として保存しておいて差分検出するスナップショットテストの一種
  • マネジドサービスになったものとしては Chromatic が有名(というか他を知らない)
  • OSS でも reg-suit & storycap というツール + S3 などのオブジェクトストレージ組み合わせると比較的簡単に構築できる

Interaction Test + VRT = ?

  • Storybook だけだと難しい「ユーザー入力に伴うUIの状態」を担保してくれる
  • アサーション書いてないところも含めて意図せずUI崩れてないか担保してくれる

ちなみに、VRT って Interaction Test の操作ちゃんと待ってくれるの?flaky にならない?というのが気になったが、こちらの記事で reg-suite と storycap の作者が特に問題なかったと言及していた。

Storybook の play function と VRT - Qiita

わからないこと

というわけで非常に便利そうではあるが、元々 react-testing-library + Jest で書いてたテストとの棲み分けがどうなるのかはちょっとまだ整理ができていない。
play function 内にアサーションも書けるので、極論すべてのコンポーネントのテストを play function に寄せることもできるのでは?と思ったけどどうなんだろう。そうなると実行速度だけが判断基準か。

takepepe さんのこちらの記事では、逆に play function を定義した Story を Jest に取り込む、とある。この場合あくまでテストは Jest 側に集約するイメージだと理解した。

参考リンク