🍣

Storybookで状態管理をモックする方法

2025/03/13に公開

Storybookで状態管理をモックする方法

Storybookは単体UIコンポーネントの開発とテストに非常に有用なツールですが、実際のアプリケーションでは多くのコンポーネントがReduxなどの状態管理ライブラリに依存しています。この記事では、実践的なコード例を元に、Storybookで効果的にRedux状態をモックする方法を解説します。

目次

  1. 基本的なアプローチ
  2. コード解説
  3. デコレーターを使ったモック
  4. ストーリーごとに異なる状態を提供する
  5. パフォーマンスとベストプラクティス

基本的なアプローチ

ReduxストアをStorybookでモックする際の基本的な考え方は次の通りです:

  1. 実際のReduxストアの代わりにモックストアを作成する
  2. モックデータを準備し、初期状態としてストアに注入する
  3. モックストアをProviderコンポーネントでラップし、ストーリーに提供する

これにより、実際のAPIやバックエンドに依存せずに、UIコンポーネントを様々な状態でテストできます。

コード解説

提供されたコードは商品支払い画面のコンポーネントのStorybookストーリーです。このコードでは以下の要素が含まれています:

  • ShopItemContentコンポーネントの表示に必要なモックデータ
  • Reduxストアをモックするためのデコレーター
  • 異なる状態(クーポン割引あり)のストーリー

まず、モックデータが定義されています:

const mockData: ShopItemProps = {
  total: 2,
  shop_items: [
    // 商品情報の配列
  ],
};

// 支払い情報のモックレスポンス
const mockCouponList: CouponList = {
    coupons: [
      { name: "クーポン1", value: 100 },
      // その他のクーポン情報
    ],
};

デコレーターを使ったモック

このコードの核心部分は、デコレーターを使ったReduxストアのモックです:

export default {
  title: "organisms/ShopItemContent",
  component: ShopItemContent,
  decorators: [
    (Story, context) => {
      // Storybookのパラメータからストア状態を取得
      const couponListState = context.parameters.couponList;

      // モックストアの作成
      const store = configureStore({
        reducer: {
          payment: (
            state = {
              couponList: couponListState,
            },
            action
          ) => state,
        },
      });

      // デバッグ用にストア状態をコンソールに出力
      if (context.name) {
        console.log(`Store state for ${context.name}:`, store.getState());
      }

      return (
        <Provider store={store}>
          <Story />
        </Provider>
      );
    },
  ],
} as ComponentMeta<typeof ShopItemContent>;

このデコレーターは以下のことを行っています:

  1. Storybookのコンテキストからパラメーターを取得
  2. configureStoreを使用してモックストアを作成
  3. 簡易的なreducerを定義(アクションを無視し、常に同じ状態を返す)
  4. 作成したストアをProviderでラップしてStoryを提供

重要なポイントとして、このreducerは単にモック状態を保持するだけのものであり、実際のアプリケーションのreducerロジックは含まれていません。これはStorybookの目的がUIのテストであり、ロジックのテストではないためです。

ストーリーごとに異なる状態を提供する

コードの後半では、異なる状態をテストするための複数のストーリーが定義されています:

// 基本的なテンプレート
const Template: ComponentStory<typeof ShopItemContent> = (args) => (
  <Provider store={store}>
    <ShopItemContent {...args} />
  </Provider>
);

// 基本的なレンダリングのストーリー
export const Render = Template.bind({});
Render.args = {
  shop_items: mockData.shop_items,
  total: 1,
};

// クーポン割引ありのストーリー
export const WithDiscountCoupons: Story = {
  parameters: {
    couponList: mockCouponList,
  },
  render: (args) => (
    <PaymentConfirmationContent
      shop_items={mockData.shop_items}
      total={1}
    />
  ),
};

特に注目すべき点は、WithDiscountCouponsストーリーのparametersプロパティです。ここで設定されたmockCouponListは、先ほどのデコレーター内でcontext.parameters.mockCouponListとして取得され、モックストアの初期状態として使用されます。

このアプローチにより、各ストーリーに対して異なるRedux状態を簡単に提供できます。

パフォーマンスとベストプラクティス

このコードから学べるベストプラクティスをいくつか挙げます:

  1. デバッグ情報の出力:

    if (context.name) {
      console.log(`Store state for ${context.name}:`, store.getState());
    }
    

    ストーリーごとの状態をコンソールに出力することで、デバッグが容易になります。

  2. パラメーターによる状態注入:
    Storybookのパラメーターシステムを利用して、ストーリーごとに異なる状態を注入しています。これにより、多様なシナリオを簡単にテストできます。

  3. モックリデューサーの簡略化:
    実際のリデューサーをモックする代わりに、シンプルな状態返却関数を使用しています。これはStorybookの目的に合致しており、不必要な複雑さを避けています。

まとめ

Storybookで効果的にRedux状態をモックするには:

  1. configureStoreを使用してモックストアを作成する
  2. デコレーターを使用してストーリーにモックストアを提供する
  3. Storybookのパラメーターシステムを活用して、ストーリーごとに異なる状態を注入する
  4. デバッグ情報を適切に出力し、問題の早期発見に役立てる

これらの手法を活用することで、Reduxに依存するコンポーネントも効率的にStorybookでテストできるようになります。さらに、このアプローチは他の状態管理ライブラリ(MobX、RecoilなどのContext APIベースのもの)にも応用可能です。

Discussion