Vinyl

Storybookのloaderが期待通りに動作しなかった(2025/2/21時点)

はじめに

Storybook の loaders 機能を利用して、React Testing Library でストーリーを流用しようと試みましたが、期待通りに動作しなかったため、その原因について調べました。

環境

以下のバージョンを使用しています。

"storybook": "8.5.3"
"@testing-library/react": "16.2.0",
"msw": "^2.7.0",
"msw-storybook-addon": "^2.0.4",

なにを実現しようとしたか

以下のようなPageコンポーネントに対してテストを書こうとしていました。

このコンポーネントは、最上位層のpage.tsxでデータ取得処理を行い、その結果をpropsとして受け取ることを想定しています。

export type Props = React.ComponentProps<typeof Posts>
 
export const Page = ({ ...props }: Props) => {
  return (
    <section aria-label="記事一覧">
      <HeaderWithLink title="Posts" />
      <Posts posts={props.posts} />
    </section>
  )
}

mswとStorybook を組み合わせて、データ取得処理を含むストーリーを作成しました。具体的には、parameters.msw.handlers で API をモック化し、loaders で非同期データをフェッチしてコンポーネントに渡す形です。

import * as Home from '.'
 
const meta: Meta<typeof Home.Page> = {
  title: 'pages/Home',
  component: Home.Page,
  parameters: {
    msw: {
      handlers: [handler()],
    },
  },
  loaders: [
    async () => {
      const posts = await fetch("hogehoge")
      const props = { posts } satisfies Home.Props
      return { props }
    },
  ],
}
export default meta
type Story = StoryObj<typeof Home.Page>
 
export const Default: Story = {
  render: (_, { loaded }) => {
    return <Home.Page {...loaded.props} />
  },
}

Storybook 上では問題なく動作し、モックされたデータが Default ストーリーに反映され、正しく描画されることを確認できました。

次に、このストーリーを React Testing Library で流用し、テストに利用しようとしました。以下のように実装しました

import { composeStories } from '@storybook/react'
import * as Stories from './index.stories'
import { setupMockServer } from '@/mocks/server';
...
 
setupMockServer(handler())
 
const { Default } = composeStories(Stories)
 
describe('Page Component', () => {
 
  test('主要コンテンツの表示', async () => {
    render(<Default />)
    await waitFor(() => {
      expect(
        screen.getByRole('region', { name: '記事一覧' }),
      ).toBeInTheDocument()
    })
  })
})

しかし<Default >は特にデータを持っておらず、正しく描画されませんでした。

原因

Storybook の loaders 機能は、Jest 環境では公式にサポートされていませんでした。そのため、composeStories を使用しても context.loadedundefined となり、非同期データがコンポーネントに渡されない状態となってしまうようでした。

これから解決されるらしい

最近、以下のStorybook のPRにてloaders の Jest 環境でのサポートが追加されてました(反映はまだ)。

Portable stories: Add support for loaders by yannbf · Pull Request #26027 · storybookjs/storybookCloses #22633 What I did This PR introduces a new method to a composed story called load. It&#39;s an async function that will execute the storie&#39;s loaders and make sure that once it renders,...favicon icongithub.com

この PR で、composeStories で生成されたストーリーに新しいメソッド load が追加されおり、このメソッドを使用することで、loaders の結果を Jest 環境でも反映できるようになっているようです。

PRの内容によると以下のように、load メソッドを明示的に呼び出すことで、非同期データをテストに利用できるみたいです。

test('主要コンテンツの表示', async () => {
  await Default.load(); // loaders を実行
  render(<Default />); // コンポーネントをレンダリング
 
  await waitFor(() => {
    expect(
      screen.getByRole('region', { name: '記事一覧' }),
    ).toBeInTheDocument();
  });
});

まとめ

  • Storybook の loaders 機能は、現状Jest 環境で正しく動作しない。
  • 最近の PR により、load メソッドが追加されていたので近い未来にこの問題が解決されるっぽい。

参考

Loaders | Storybook docsStorybook is a frontend workshop for building UI components and pages in isolation. Thousands of teams use it for UI development, testing, and documentation. It's open source and free.favicon iconstorybook.js.org

Portable stories: Add support for loaders by yannbf · Pull Request #26027 · storybookjs/storybookCloses #22633 What I did This PR introduces a new method to a composed story called load. It&#39;s an async function that will execute the storie&#39;s loaders and make sure that once it renders,...favicon icongithub.com

Mock Service WorkerAPI mocking library for browser and Node.jsfavicon iconmswjs.io

@storybook/testing-reactTesting utilities that allow you to reuse your stories in your unit tests. Latest version: 2.0.1, last published: 2 years ago. Start using @storybook/testing-react in your project by running `npm i @storybook/testing-react`. There are 18 other projects in the npm registry using @storybook/testing-react.favicon iconwww.npmjs.com