Storybookで状態管理をモックする方法
Storybookで状態管理をモックする方法
Storybookは単体UIコンポーネントの開発とテストに非常に有用なツールですが、実際のアプリケーションでは多くのコンポーネントがReduxなどの状態管理ライブラリに依存しています。この記事では、実践的なコード例を元に、Storybookで効果的にRedux状態をモックする方法を解説します。
目次
基本的なアプローチ
ReduxストアをStorybookでモックする際の基本的な考え方は次の通りです:
- 実際のReduxストアの代わりにモックストアを作成する
- モックデータを準備し、初期状態としてストアに注入する
- モックストアを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>;
このデコレーターは以下のことを行っています:
- Storybookのコンテキストからパラメーターを取得
-
configureStore
を使用してモックストアを作成 - 簡易的なreducerを定義(アクションを無視し、常に同じ状態を返す)
- 作成したストアを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状態を簡単に提供できます。
パフォーマンスとベストプラクティス
このコードから学べるベストプラクティスをいくつか挙げます:
-
デバッグ情報の出力:
if (context.name) { console.log(`Store state for ${context.name}:`, store.getState()); }
ストーリーごとの状態をコンソールに出力することで、デバッグが容易になります。
-
パラメーターによる状態注入:
Storybookのパラメーターシステムを利用して、ストーリーごとに異なる状態を注入しています。これにより、多様なシナリオを簡単にテストできます。 -
モックリデューサーの簡略化:
実際のリデューサーをモックする代わりに、シンプルな状態返却関数を使用しています。これはStorybookの目的に合致しており、不必要な複雑さを避けています。
まとめ
Storybookで効果的にRedux状態をモックするには:
-
configureStore
を使用してモックストアを作成する - デコレーターを使用してストーリーにモックストアを提供する
- Storybookのパラメーターシステムを活用して、ストーリーごとに異なる状態を注入する
- デバッグ情報を適切に出力し、問題の早期発見に役立てる
これらの手法を活用することで、Reduxに依存するコンポーネントも効率的にStorybookでテストできるようになります。さらに、このアプローチは他の状態管理ライブラリ(MobX、RecoilなどのContext APIベースのもの)にも応用可能です。
Discussion