Storybook CSF3.0 時代のテストに備える
CSF3.0 が間近に迫っているが、まだリリース前という状況。Storybook は、Component テストと Storybook の垣根を壊す事を目指しており、CSF3.0 は Storybook に閉じた話ではなくテスト戦略に関わってくる。 現状 Storybook 環境を整備するにあたり、どう備えておくべきかを考える。
npm i -D @storybook/react@next
で beta を install し、.storybook/main.js
に以下を追加すれば CSF3.0 を試す事ができる。
module.exports = {
features: { previewCsfV3: true },
};
CSF3.0 は限りなくメタのみになる
CSF3.0 では Story を登録するために必要なものが削減され、render 関数さえ不要になる。Story の工数削減だけでなく、Meta 情報のみに絞られてポータビリティの高い普通の Object になる。
// CSF 2.0
export const PrimaryOnDark = Primary.bind({});
PrimaryOnDark.args = Primary.args;
PrimaryOnDark.parameters = { background: { default: 'dark' } };
// CSF 3.0
export const PrimaryOnDark = {
...Primary,
parameters: { background: { default: 'dark' } },
};
Story 登録のために必要な最小コードは以下。.stories.tsx
ファイルにおいて named export している Object は、これだけで登録される様になる。CSF2.0 と同様に export default
で export された component があり、この関数と named export された Object が合成されるという事になる。
// CSF 3.0 - default render function
export const Default = {};
export default { component: Button };
Story は既にテストに持ち出されている
以下ブログで言及されているとおり。Storybook とテスト、それぞれでレンダリング関数や初期値注入を重複して用意することが無駄なので、Storybook で用意した Story をテストで再利用してしまおうという動機。
@storybook/testing-react
というのがこちら。現状 @storybook/testing-react@next
で試してみたが、CSF3.0 にはまだ対応していない。 > v0.0.21 で対応していそう
CSF3.0 の再生機能
個人的に一番の目玉。testing-library でテストを書く感覚で、UI インタラクションを登録できる。play 関数は、Storybook 上でコンポーネントがマウントされるタイミングで実行される。("@storybook/react": "6.4.0-beta.1" 現在)
// CSF 3.0
import userEvent from '@testing-library/user-event';
export default { component: AccountForm }
export const Empty = {};
export const EmptyError = {
...Empty,
play: () => userEvent.click(screen.getByText('Submit'));
}
export const Filled = {
...Empty,
play: () => {
userEvent.type(screen.getById('user'), 'shilman@example.com');
userEvent.type(screen.getById('password'), 'blahblahblah');
}
}
export const FilledSuccess = {
...Filled,
play: () => {
Filled.play();
EmptyError.play();
}
}
CSF3.0 におけるインタラクションテスト
@storybook/addon-test
はまだ試せないが、play 関数内でアサーションが書ける様になる。以下ブログにあるとおりで、Storybook 上でもテストが実行可能になる。
// FormExample.stories.js|tsx
import { expect, screen, userEvent } from '@storybook/addon-test';
export default { component: FormExample }
export const Empty = {};
export const Filled = {
...Empty,
play: async () => {
await userEvent.type(screen.getById('user'), 'shilman@example.com');
await userEvent.type(screen.getById('password'), 'blahblahblah');
await userEvent.click(screen.getByText('Submit'));
await expect(handleSubmit).toHaveBeenCalledWith({
user: 'shilman@example.com',
password: 'blahblahblah',
});
}
}
【予想】インタラクションテストも jest で再利用される
CI で Storybook インタラクションテストを回す方法はまだ想像がつかないが、play 関数に書いたインタラクション・アサーションは、そのまま jest で実行可能になるはず。
Story・Jest 双方に持ち出し可能なポータブルなコードを Storybook は目指しており、インタラクション・アサーションを一箇所でメンテナンス出来ればメリットは大きい。
Storybook インタラクションテストは Storybook を中継することになるが、View を確認する必要がなければ headless な jest で実行できた方が高速。おそらく@storybook/testing-react
がその役割を担うのだと思う。
【備え】@storybook/testing-react を使っておく
jest 側で render するコンポーネントは @storybook/testing-react
を使うコードにしておくことで、将来的に双方が手厚くなる可能性が高そう。
現状では @storybook/testing-react
と play関数を同居させることは不可なので、CSF2.0 でこのあたりを意識して備えたい。
CSF3.0 は対応済みの様子
リリースノートを見る限り、@storybook/testing-react@v0.0.21
ではすでに対応している様子だった。後で試す。 試したら普通に出来た。型定義がまだ未対応な様子。
v1.0.0-next.0 の Pre リリースノートに書いてあった。
export const InputFieldFilled: Story<InputFieldProps> = {
play: async () => {
await userEvent.type(screen.getByRole('textbox'), 'Hello world!');
},
};
const { InputFieldFilled } = composeStories(stories);
test('renders with play function', async () => {
render(<InputFieldFilled />);
// play an interaction that fills the input
await InputFieldFilled.play!();
const input = screen.getByRole('textbox') as HTMLInputElement;
expect(input.value).toEqual('Hello world!');
});
実際に試す事ができたので、清書した https://zenn.dev/takepepe/articles/storybook-driven-development