15
6

More than 3 years have passed since last update.

jestを使ったCustom hooksのunit test方法 (別のCustom hooksに依存している場合)

Posted at

Custom Hooksを利用していてそれらの間に依存関係があるとき、どうやってjestのテストを書くか、です。

結論から言うと、jestのモック化の方法を覚えましょうという話でした。

前提

useAwesomeA, B, C, Dがあり、useAwesomeAからB, C, Dへの依存がある状態で、B, Dにunit testでは実行したくないロジック(サーバ通信やReact nativeだとnative moduleの処理など)があるという状態を前提としています。

useAwesomeA --+--> useAwesomeB (unit testで呼び出したく無い)
              +--> useAwesomeC
              +--> useAwesomeD (unit testで呼び出したく無い)

言語はTypescriptです。

他のCustom Hooksに依存関係が無い場合

@testing-library/react-hooksrenderHookを呼び出して結果を確認すればOKです。renderHookresult.current配下に実際に呼び出したcustom hooksの結果を入れてくれるので、この場合result.current.awesomeFuncC()を呼び出すことでuseAwesomeCのロジックをテストします。

useAwesomeC.ts
const useAwesomeC = () => {
  const awesomeFuncC: () => string = () => {
    return 'message from C!';
  };

  return {awesomeFuncC};
};

export default useAwesomeC;
useAwesomeC.test.ts
import {renderHook} from '@testing-library/react-hooks';
import useAwesomeC from '../../src/hooks/useAwesomeC';

describe('Custom Hookのテスト方法', () => {
  test('他のCustom Hookに依存していないCustom Hookのテスト', () => {
    const {result} = renderHook(() => useAwesomeC());

    expect(result.current.awesomeFuncC()).toBe('message from C!');
  });
});

renderHookの使い方を理解すれば簡単です。インストール方法やその他の情報はライブラリのサイトで確認できます。

他のCustom Hooksに依存がある場合

jestで呼び出したくないモジュールをモック化します。モック化の方法はいくつかあるようですが今回はtestコードの中でモック化しています。

手順は以下の通りです。
1. テストコードでモックしたいモジュールをimportする
2. jest.mockでモジュールをモック化する
3. mockImplementation()でモック化したモジュールが何を返すか実装する
4. mockClear()でテスト完了時に呼び出された回数などの記録をクリア

useAwesomeA.ts
import {useCallback} from 'react';
import useAwesomeB from './useAwesomeB';
import useAwesomeC from './useAwesomeC';
import {useAwesomeD} from './useAwesomeD';

const useAwesomeA = () => {
  const {awesomeFuncB} = useAwesomeB();  // unit testでは呼び出したくない
  const {awesomeFuncC} = useAwesomeC();
  const {awesomeFuncD} = useAwesomeD();  // unit testでは呼び出したくない

  const awesome = useCallback(() => {
    const messageB = awesomeFuncB();
    const messageC = awesomeFuncC();
    const messageD = awesomeFuncD();
    return `Hellow! Here are messages for you!: ${messageB}, ${messageC}, ${messageD}`;
  }, [awesomeFuncB, awesomeFuncC, awesomeFuncD]);

  return {awesome};
};

export default useAwesomeA;
useAwesomeA.test.ts
import {renderHook} from '@testing-library/react-hooks';
import useAwesomeA from '../../src/hooks/useAwesomeA';
import useAwesomeB from '../../src/hooks/useAwesomeB';   // 一度普通にimportする(default export)
import {useAwesomeD} from '../../src/hooks/useAwesomeD'; // 一度普通にimportする(名前付きexport)

jest.mock('../../src/hooks/useAwesomeB'); // moduleをmockする
jest.mock('../../src/hooks/useAwesomeD'); // moduleをmockする

describe('Custom Hookのテスト方法', () => {
  const mockMessageB = 'message from mockB';
  const mockMessageD = 'message from mockD';

  beforeAll(() => {
    // mock moduleで何を返すかを設定する
    (useAwesomeB as jest.Mock).mockImplementation(() => ({
      awesomeFuncB: jest.fn().mockReturnValue(mockMessageB),
    }));

    (useAwesomeD as jest.Mock).mockImplementation(() => ({
      awesomeFuncD: jest.fn().mockReturnValue(mockMessageD),
    }));
  });

  beforeEach(() => {
    // 呼び出し回数などをクリア
    (useAwesomeB as jest.Mock).mockClear();
    (useAwesomeD as jest.Mock).mockClear();
  });

  test('Custom Hookが別のCustom Hookを呼び出しているときののテスト', () => {
    const {result} = renderHook(() => useAwesomeA());

    expect(result.current.awesome()).toBe(
      `Hellow! Here are messages for you!: ${mockMessageB}, message from C!, ${mockMessageD}`,
    );
  });
});

ふと、default exportと名前付きexportでモック方法に違いがあるかなと思って実験しましたが、何も変わりませんでした。

以上、誰かの役に立てば幸いです。

15
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
6