Closed10

Storybook CSF3.0 時代のテストに備える

TakepepeTakepepe

CSF3.0 が間近に迫っているが、まだリリース前という状況。Storybook は、Component テストと Storybook の垣根を壊す事を目指しており、CSF3.0 は Storybook に閉じた話ではなくテスト戦略に関わってくる。 現状 Storybook 環境を整備するにあたり、どう備えておくべきかを考える。

https://storybook.js.org/blog/component-story-format-3-0/

npm i -D @storybook/react@next で beta を install し、.storybook/main.js に以下を追加すれば CSF3.0 を試す事ができる。

module.exports = {
  features: { previewCsfV3: true },
};
TakepepeTakepepe

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 };
TakepepeTakepepe

Story は既にテストに持ち出されている

以下ブログで言及されているとおり。Storybook とテスト、それぞれでレンダリング関数や初期値注入を重複して用意することが無駄なので、Storybook で用意した Story をテストで再利用してしまおうという動機。

https://medium.com/storybookjs/testing-lib-storybook-react-8c36716fab86

@storybook/testing-react というのがこちら。現状 @storybook/testing-react@next で試してみたが、CSF3.0 にはまだ対応していない。 > v0.0.21 で対応していそう

https://storybook.js.org/addons/@storybook/testing-react

TakepepeTakepepe

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();
  }
}
TakepepeTakepepe

CSF3.0 におけるインタラクションテスト

@storybook/addon-test はまだ試せないが、play 関数内でアサーションが書ける様になる。以下ブログにあるとおりで、Storybook 上でもテストが実行可能になる。

https://storybook.js.org/blog/interaction-testing-sneak-peek/

// 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',
    });
  }
}
TakepepeTakepepe

【予想】インタラクションテストも jest で再利用される

CI で Storybook インタラクションテストを回す方法はまだ想像がつかないが、play 関数に書いたインタラクション・アサーションは、そのまま jest で実行可能になるはず。

Story・Jest 双方に持ち出し可能なポータブルなコードを Storybook は目指しており、インタラクション・アサーションを一箇所でメンテナンス出来ればメリットは大きい。

Storybook インタラクションテストは Storybook を中継することになるが、View を確認する必要がなければ headless な jest で実行できた方が高速。おそらく@storybook/testing-react がその役割を担うのだと思う。

TakepepeTakepepe

【備え】@storybook/testing-react を使っておく

jest 側で render するコンポーネントは @storybook/testing-react を使うコードにしておくことで、将来的に双方が手厚くなる可能性が高そう。

現状では @storybook/testing-react と play関数を同居させることは不可なので、CSF2.0 でこのあたりを意識して備えたい。

TakepepeTakepepe

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://github.com/storybookjs/testing-react/releases/tag/v1.0.0-next.0

このスクラップは2021/09/30にクローズされました