LoginSignup
25
20

More than 1 year has passed since last update.

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);
  });
});
25
20
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
25
20