Reactを使ったコンポーネントのテストのやり方を知らなかったので、やってみた内容をメモ。hatena/go-Intern-Diaryをお試し環境として利用した。結果は https://github.com/shibayu36/go-Intern-Diary/pull/5/files
テスト概要 – Reactを最初の資料としながら色々調査していった。
どのライブラリを使うか
- Jest: テストランナーやアサーションライブラリ、テスト便利グッズ用途として。Reactの公式でもおすすめされているので利用する。
- enzyme: Reactコンポーネントをテストするための便利グッズ用途として。
- 公式では@testing-library/reactがおすすめされている参考ようだが、renderメソッドの返り値でgetByTextとかDOM操作用の便利関数が返ってくるモデルのインターフェースが気に入らなかったのでenzymeにした
- @apollo/react-testing: Apolloのテスト用の便利グッズとして
- @testing-library/react: enzymeを使ったとしても、act()などの関数が必要になることがあるため
テストを書くまでの手順
以下の手順で始められる。
- 必要なpackageをインストールする
- Jestのセットアップをする
- テストを書く
必要なpackageをインストールする
レポジトリ内の package.json を参考に。TypeScriptで作っているので型のためのpackageも入れる必要あり。
yarn add -D @apollo/react-testing @testing-library/react enzyme enzyme-adapter-react-16 jest ts-jest # 型 yarn add -D @types/enzyme @types/enzyme-adapter-react-16 @types/jest # 使っても使わなくても良いもの yarn add -D waait
Jestのセットアップをする
jest.config.jsの用意と、enzymeのためにsetupTests.tsを書けばOK。
jest.config.js。TypeScriptをテストで使うために変換の設定と、テスト時の最初に読み込むファイル(setupTests.ts)の設定をしている。
module.exports = { preset: "ts-jest", verbose: true, roots: ["<rootDir>/ui/src"], transform: { "^.+\\.tsx?$": "ts-jest" }, setupFilesAfterEnv: ["<rootDir>/ui/src/setupTests.ts"], globals: { "ts-jest": { tsConfig: "ui/tsconfig.json" } } };
ui/src/setupTests.ts。enzymeのセットアップのために呼びたいコードを書いている。
import { configure } from "enzyme"; import Adapter from "enzyme-adapter-react-16"; configure({ adapter: new Adapter() });
テストを書く
あとは自分の書きたいテストを書くだけ。
ブログ一覧ページのテストを簡単に書いたものは以下の通り。
ui/src/pages/__tests__/diaries.test.tsx
import React from 'react'; import wait from 'waait'; import { mount } from 'enzyme'; import { getMyDiariesQuery, Diaries } from '../diaries'; import { MockedProvider } from '@apollo/react-testing'; import { act } from '@testing-library/react'; import { BrowserRouter as Router } from 'react-router-dom'; describe('<Diaries />', () => { it('正常にレンダリングできる', async () => { const mocks = [ { request: { query: getMyDiariesQuery }, result: { data: { visitor: { id: '1', name: 'shibayu36', diaries: [ { id: '2', name: 'shibayu36の日記' }, { id: '3', name: 'shibayu36のブログ' } ] } } } } ]; const wrapper = mount( <MockedProvider mocks={mocks} addTypename={false}> <Router> <Diaries /> </Router> </MockedProvider> ); await act(async () => { await wait(0); }); wrapper.update(); expect(wrapper.find('h1').text()).toBe('shibayu36のダイアリー一覧'); const diaries = wrapper.find('Link'); expect(diaries).toHaveLength(3); expect(diaries.at(0).prop('to')).toBe('/diaries/2'); expect(diaries.at(0).find('p').text()).toBe('shibayu36の日記'); expect(diaries.at(1).prop('to')).toBe('/diaries/3'); expect(diaries.at(1).find('p').text()).toBe("shibayu36のブログ"); }); });
ポイントとしては
- フルレンダリングしてテストしたいならenzymeのmountを使う
- 子コンポーネントをモックしてくれるshallow renderingというのもある
- MockedProviderを使うことで、Apolloのリクエストをモックできる
- React Hooksを使う場合、レンダリングが実行されるようなユーザーインタラクションがある操作をする場合は、@testing-library/reactのactでくるまなければならない
- enzymeは .simulate(), .setProps(), .setContext(), .invoke()あたりのメソッドをactで包んでくれているので、それらのメソッドを用いる場合は包まなくても良い
- 今回は
await wait(0)
の部分でレンダリングが実行されるので、上記のメソッドを使っておらず、actで包んでからwrapper.update()を明示的に実行する必要がある。なぜawait wait(0)
しているかはNodeのイベントループを理解するために遊んだ & Apolloのテストでawait wait(0)するとなぜデータがロードされるか - $shibayu36->blog;を参照。
これでなんとなくテストをするイメージを掴むことができた。
まとめ
今回はReact + Apolloを使ったコンポーネントのテストをする方法について簡単な実践をして、分かったことを書いてみた。さらにいろんなテストを書くためには以下のような資料を読むと良いだろう。
- React Hooks + TypeScript + React Apollo Hooks + Jest + enzyme を使ってテストを書く - Qiita
- 今回のような構成で、より詳しく具体的にテスト手法を書いてくれている
- Airbnb Engineering & Data Science
- Enzymeで何ができるかはここを見よう
- はじめましょう · Jest
- Jestで何ができるかはここを見よう