hooksのテストを通常の単体テストで書こうとすると、下記のようなエラーが出てしまいます。
Invariant Violation: Hooks can only be called inside the body of a function component.
hooksは、本来はComponentの世界でしか機能しないためです。
hooksでテストを行う際は、擬似的にComponentの世界を作り出してテストを実現するツール
@testing-library/react-hooks
を使います。
下準備
ライブラリのインストール
下記コマンドを実行します。
yarn add -D @testing-library/react-hooks
コンポーネントとカスタムフックに処理を分離する
コンポーネントに依存したhooksをテストする場合、単体テストではなくComponentテストになります。
(※ Componentテストは明日の記事で紹介します!)
そのため、Componentとhooksの処理は分離しておきましょう。
ステートを更新するシンプルなhooksのテストの実装
下記のようなとてもシンプルなStateを更新するhooksがあるとします。
export const useCounter = () => {
const [count, setCount] = useState(0);
const increment = useCallback(() => setCount((prev) => prev + 1), []);
const decrement = useCallback(() => setCount((prev) => prev - 1), []);
return {
count,
increment,
decrement,
};
};
1. renderHook
を使って、hooksの返り値を使う
renderHook
の引数にhooksを実行するコールバック関数を渡すと、オブジェクトが返ってきます。
そのオブジェクトの中にあるresult
に、hooksが返すステートや関数が含まれています。
import { useCounter } from './use-counter'
import { renderHook } from '@testing-library/react-hooks';
describe('useCounterのテスト', () => {
test('increment関数を実行すると、countが1増える', () => {
const { result } = renderHook(() => useCounter());
});
});
2. 現在のステートの値を取得する
resultオブジェクトに含まれるcurrent
に、現在のステート・関数の値が格納されています。
result.current.count
3. ステート更新関数を実行する
ステートの更新関数を実行したい時は、act
関数の中で実行する必要があります。
act(() => {
result.current.increment();
});
4. 完成
describe('useCounterのテスト', () => {
test('increment関数を実行すると、countが1増える', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
});
補足
cleanup()
はレンダリングされたフックをアンマウントする関数です。
hooksが副作用をもっている可能性があるため、テスト毎にcleanupした上でテストをすると良いです!
import { useCounter, cleanup } from './use-counter'
import { renderHook } from '@testing-library/react-hooks';
describe('useCounterのテスト', () => {
beforeEach(() => {
cleanup();
});
test('increment関数を実行すると、countが1増える', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
test('decrement関数を実行すると、countが1減る', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.decrement();
});
expect(result.current.count).toBe(-1);
});
});