React カスタムフックをテストする

カスタムフックとは

React Hooksでは、ユーザー独自のカスタムフックを作成することができます。この機能により、簡単にUIコンポーネントからロジックを切り離し、再利用できるようになりました。
しかし、カスタムフックはReactコンポーネント内でしか動作しないため、ユニットテストを行うには若干コツが必要になるようです。

というわけで、カスタムフックをテストしてみましょう。

環境構築

いつもの(Create React APP + TypeScript)。

カスタムフックの作成

環境構築ができたら、カスタムフックを作成しましょう。

import { useState } from 'react'  

const useCount = (): [number, () => void, () => void] => {  
  const [count, setCount] = useState(0)  

  const increment = () => setCount(count + 1)  
  const decrement = () => setCount(count - 1)  

  return [count, increment, decrement]  
}  

export default useCount  

カスタムフックの名前は、頭にuseを付けなくてはなりません。「これはカスタムフックなんだぞー」と分かりやすいようにするためです。
ここで作成したフックは、数値の処理操作をまとめたものです。例えば以下のように利用します。

// カスタムフック利用例  
import React from 'react'  
import useCount from './useCount'  

const App: React.FC = () => {  
  const [count, increment, decrement] = useCount()  

  return (  
    <div className="App">  
      <p>カウント:{count}</p>  
      <button onClick={() => increment()}>1増やす</button>  
      <button onClick={() => decrement()}>1減らす</button>  
    </div>  
  )  
}  

export default App;  

簡単なカウンターアプリですね。ボタンを押せば1増えたり減ったりします。このように、UIから処理を切り出して別々に記述できることが、カスタムフックを使うことの大きな利点でしょう。

カスタムフックのテスト

まずはReact Testing Libraryを追加しましょう。ちなみに公式でもオススメされています

$ yarn add -D @testing-library/react  

React Testing Libraryを使うことにより、いわゆるボイラーテンプレートを省略することができるようになります。
あとはテストを書くだけです。出来上がったテストはこちらになります。

import React from 'react'  
import { render, fireEvent } from '@testing-library/react'  
import useCount from './useCount'  

it('useCountのユニットテスト', () => {  
  const TestComponent: React.FC = () => {  // テスト用Reactコンポーネント作成  
    const [count, increment, decrement] = useCount()  

    return (  
      <div>  
        <p title="count">{count}</p>  
        <button title="increment" onClick={() => increment()}></button>  
        <button title="decrement" onClick={() => decrement()}></button>  
      </div>  
    )  
  }  

  const { getByTitle } = render(<TestComponent />)  // 仮想DOMにレンダリング  

  const count = getByTitle("count")  // HTML要素を取得  

  expect(count!.textContent).toEqual("0")  

  fireEvent.click(getByTitle("increment"))  // クリックイベントを発火  
  expect(count!.textContent).toEqual("1")  

  fireEvent.click(getByTitle("decrement"))  
  fireEvent.click(getByTitle("decrement"))  
  expect(count!.textContent).toEqual("-1")  
})  

冒頭でも書きましたが、カスタムフックはReactコンポーネント内でしか動作しません。また、カスタムフックの値や関数を外部から直接呼び出すこともできません(もしかしたら私が知らないだけ)。そのため、今回はテスト用のコンポーネントを作成し、HTML要素から間接的に値や関数を呼び出すことでテストを行っています。

引数を変えて何度も関数を呼び出したいこともあると思います。そのときは、個別に引数を設定した関数を用意してボタンにバインドし、クリックイベントで順番に呼び出すといった方法が使えると思います。ボタンをメソッド替わりにするということですね。

HTML要素取得イベント発火には柔軟なAPIが用意されているので、詳細は公式を見てください。

まとめ

カスタムフックを使うとロジックが柔軟に組めるようになります。宣言的UIはReact Hooksで完成に至るという記事もありますし、今後のReact開発ではカスタムフックの知識が不可欠になるかと思います。
カスタムフックで複雑な処理を行うためにも、テスト方法はマスターしておきたいところですね。