useSelectorのMockの仕方


なぜこの記事を書いたか

reactに関するテストの勉強をしていて、useSelectorのモックの仕方で詰まりました。この経験からこの記事を残そうを思います。

useSelectorのモックの仕方はいくつか記事があるとは思うのですが、具体的に今回詰まったのは、一つのコンポーネント(or カスタムフック)で複数回useSelectorを呼び出す場合でした。この記事では、そんな複数回のuseSelectorの呼び出しにも対応させています。

ケース

早速ですが、複数回のuseSelectorの呼び出しとは何かというと、以下のようなケースです。

useMyCloset.js
const useMyCloset = () => {
  const isThinkingTops = useSelector(state => state.tops.isThinking);
  const tShirts = useSelector(state => state.tops.tShirts);

  const loadMyTshirts = () => {
    if (isThinking) return null;
     return tShirts;
  }

  return {
    loadMyTshirts
  }
}

useSelectorによって値を取得する際は、一度のuseSelectorで、objectを取得して、その値を使用するのではなく、何度もuseSelectorを呼び出して一番小さい単位で取得することが推奨されています。(参考)

よって、先述のコードは以下のように書くと、あまりよろしくないということになります。(なぜこのようにした方が良いかは、本記事では省きます。)

useMyCloset.tsx.js
const useMyCloset = () => {
  // ↓ 一度のuseSelector呼び出しで値を取得している
  const tops = useSelector(state => state.tops);

  const loadMyTshirts = () => {
    if (tops.isThinkingTops) return null
     return tops.tShirts
  }

  return {
    loadMyTshirts
  }
}

じゃあどうするのか

さて、少し話題が逸れましたが、複数回のuseSelector呼び出しは、以下のように、mockImplementationを使った方法でモックできます。

useSelectorは、引数にrootStateを取るselector関数を受け取り、それを実行するだけです。
よって, useSelectorの振る舞いをモックした上で、selector関数でエラーが出ないように、dummyのRootStateをselector関数の引数に渡すようにします。

useMyCloset.test.js
// 他import
import {useSelector} from 'react-redux';

jest.mock('react-redux');
const useSelectorMock = useSelector as jest.Mock;
const dummyStore = {
  tops: {
    isThinking: false,
    tShirts: ['tShirtsA', 'tShirtsB', 'tShirtsC']
  }
}

beforeEach(() => {
  useSelectorMock.mockImplementation(selector => selector(dummyStore))
});

afterEach(() => {
  jest.resetAllMocks();
});

describe('useMyCloset', () => {
  it('testA', () => {
    // some test
  });
});


他にも何かもっといい方法があれば教えてくださいませ〜〜!!