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-hooks
のrenderHook
を呼び出して結果を確認すればOKです。renderHook
はresult.current
配下に実際に呼び出したcustom hooksの結果を入れてくれるので、この場合result.current.awesomeFuncC()
を呼び出すことでuseAwesomeCのロジックをテストします。
const useAwesomeC = () => {
const awesomeFuncC: () => string = () => {
return 'message from C!';
};
return {awesomeFuncC};
};
export default useAwesomeC;
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コードの中でモック化しています。
手順は以下の通りです。
- テストコードでモックしたいモジュールをimportする
-
jest.mock
でモジュールをモック化する -
mockImplementation()
でモック化したモジュールが何を返すか実装する -
mockClear()
でテスト完了時に呼び出された回数などの記録をクリア
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;
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でモック方法に違いがあるかなと思って実験しましたが、何も変わりませんでした。
以上、誰かの役に立てば幸いです。