📖

StorybookでuseSelectorのモックを追加

2021/09/09に公開

Storybook、React初心者です。
Storyook内でuseSelector部分をモックにして表示パターンを作成した時のメモです。
(そもそもStorybookでそんなことするのが適切でないかもしれません。)

前提

  • "react": 16.13.1
  • "storybook": 5.3.19
  • "react-redux": 7.2.0
Fooコンポーネント(一部抜粋)
import * as selectors from "src/selectors"; // Stateのデータを加工してる関数がまとまっている

export const Foo: React.FC<Props> = () => {
...
const something = useSelector(selectors.getSomeState);
...
}
src/selectors(一部抜粋)
import { State } from "src/reducers";

export const getSomeState = (state: State) => state.User.someParametor;
src/reducers(一部抜粋)
// Stateに追加するオブジェクト設定
export type UserState = { 
 someParametor: {
  avator: String;
  statuses: [];
 }
 ....
}

Storybookでエラーになった書き方

foo.stories.tsx
const Template = () => <Foo {...args1} />;

export const pettern1 = Template.bind({});

下記のエラーとなり、Storybookで描画できませんでした。
could not find react-redux context value; please ensure the component is wrapped in a <Provider>
エラー時のキャプチャ

コンポーネントを表示させるためだけのダミーのStoreの書き方

表示させるためには<Provider>でラップする必要があります。
空の<Provider>を使うことで表示されました。

foo.stories.tsx
import React from "react";
import { Story, Meta } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { Provider } from "react-redux";
import { createStore } from "redux";
import { reducers } from "src/reducers";
import { createBrowserHistory as createHistory } from "history";
import Foo, { Props } from "path/to/file";

// 空のstoreを作成
const dummyStore = createStore(reducers(createHistory()));

// ProviderにStoreを接続してコンポーネントをラップ
const Template: Story<Props> = (args) =>
  <Provider store={dummyStore}>
    <Foo {...args} />
  </Provider >;

const something = useSelector(selectors.getSomeState);の右になにも入らない状態となります。

実際のStoreのパターンをmockしてパターンを作成する

実際のStoreのパターンを表現できればテストパターンの確認ができます。
参考リンクのReduxを接続の記述が大変役にたちました。お礼申し上げます。

foo.stories.tsx
import React from "react";
import { Provider } from "react-redux";
import { store } from "src/store"; // 定義されたstore 
import { UserState } from "src/reducers";
import Foo, { Props } from "path/to/file";

// Storeのmockを作成
// getState()の値に引数のstateをマージすることでStoreの戻り値がカスタマイズできる
const createMock = (state: UserState) => {
  return {
    ...store,
    getState: () => {
      return { ...store.getState(), ...state };
    },
  };
};

// Stateのパターン作成
// 取得したいStateの構造と同じ。複製して値をいじることでイレギュラーなケースなどのパターンが作れる
const testStore = createMock({
  someParametor: {
    avator: "default",
    statuses: [],
  },
});

// console.log(testStore.getState())でStoreの値は確認できる

// TemplateとsomePatternを対にしてパターンを作成
const Template: Story<Props> = (args) =>
  <Provider store={testStore}> // ←パターン別に差し替える
    <Foo {...args} />
  </Provider >;
  
export const somePattern = Template.bind({}); // 

const something = useSelector(selectors.getSomeState);にtestStoreの値が代入されます。

作成したパターンはjestにimportすることが可能になります。
制作環境の都合でバージョンが古いためjestへの組み込みは未対応です。
対応可能になったら追記したいと思います。

参考リンク

https://note.com/t09tanaka/n/n259472d1afc4

Discussion