✅RTL(React Testing Library)、ちゃんと理解して使おうか。
まえがき
今いる開発現場ではReactを採用しており、方針として「テストちゃんと書こうぜ(´_ゝ`)」という感じなので、よくテストコードを書いたりレビューする機会は多い。
しかし、Reactのテストで欠かせない要素であるReact Testing Library, Enzymeについて正直ちゃんと理解できていない/他者に説明できる自信がない/雰囲気でやってる感否めない(´_ゝ`)ので、本稿で克服する。
インストール
npm install --save-dev @testing-library/react
// toBeInTheDocument使いたい
npm install @testing-library/jest-dom --save-dev;
RTLなし/あり でどんなコードになるのか?
React公式が公開しているテストのレシピ集はRTLを使っていないソースコードなので、これを題材に分析する。ソースコードと詳細はリンク先を見てもらった方がわかりやすいので、ここでは分析結果をまとめる。
01. 土台となるdiv要素の生成&ReactコンポーネントをDOM反映
import { render } from "react-dom";
import App from "./App";
test(("render App") => {
const container = document.createElement("div");
render(<App />, container);
});
Reactコンポーネントをレンダリング対象となるdiv要素をdocument.createElement("div")
で生成し、react-domのrender関数でReactコンポーネントをDOMに反映している。
import { render } from "@testing-library/react";
import App from "./App";
test("render App", async () => {
render(<App />);
👉RTLだと@testing-library/reactのrender関数で以下のことを網羅する。
・土台のdiv要素をDOM上に生成。
・引数のReactコンポーネントをDOM(土台のdiv要素)に反映。
02. DOM内の要素取得
import { render } from "react-dom";
import App from "./App";
test(("find the button to count up") => {
const container = document.createElement("div");
render(<App />, container);
// find the button
const button = document.querySelector("[data-testid=toggle]");
// ...
});
DOM要素参照でお馴染みのdocument.querySelector
関数を使って参照する。
import { render, screen } from "@testing-library/react";
import App from "./App";
test("find the button to count up", async () => {
render(<App />);
// find the button
const button = screen.getByTestId("countupBtn");
// ...
👉RTLだとscreenオブジェクトの関数(getByXxx/findByXxx/queryByXxx)でらくらく参照。※内部でdocument.querySelector
関数などを使っているのだろう。
03. イベント発火
import { render } from "react-dom";
import { act } from "react-dom/test-utils";
import App from "./App";
test(("find the button to count up") => {
const container = document.createElement("div");
render(<App />, container);
// find the button
const button = document.querySelector("[data-testid=toggle]");
// click the button
act(() => {
button.dispatchEvent(new Event("click"));
});
});
DOM APIのEventTarget.dispatchEvent関数を使って発火する。今回の例だとClickイベントを発火させている。
import { render, fireEvent, waitFor, screen } from "@testing-library/react";
import App from "./App";
test("find the button to count up", async () => {
render(<App />);
// find the button
const button = screen.getByTestId("countupBtn");
// click the button
await waitFor(() => {
fireEvent.click(button);
});
👉RTLだとfireEventオブジェクトの関数(click/blur/drag/etc)でらくらくイベント発火。
@testing-library/jest-dom)
04. Custom Matcher(RTLが提供する@testing-library/jest-domを下記のようにimportすることで、DOMから要素を取得するための便利なメソッドを利用することができる。
import { render, fireEvent, waitFor, screen } from "@testing-library/react";
import "@testing-library/jest-dom"; // ★ここ★ //
import App from "./App";
test("should diplay 1 after clicking the button once", async () => {
render(<App />);
// get by data-testid
const button = screen.getByTestId("countupBtn");
await waitFor(() => {
fireEvent.click(button);
});
// ★ここ★ //
expect(await screen.findByText("COUNT: 1")).toBeInTheDocument();
});
※リンク先↓に利用可能なCustom Matcherがあるので参考に。
中でも良く使うものをメモしておく。
関数名 | 役割 |
---|---|
toBeInTheDocument() |
対象要素がdocument内にあること |
toBeVisble() |
対象要素がVisbleかどうか |
toHavaAttribute(attr: string, value?: any) |
対象要素が引数の属性を持つか |
RTL関連メモ
👉act関数とRTLのwaitFor関数の話
✅act関数を使うタイミング、理由
✅RTLが提供するfireEventやrender関数は内部でact関数を利用してる。
✅RTLのwaitFor関数を使えばact関数を使わないで済むこと。
参考:React のテストを書いてたら act で囲んでよーって言われたとき
act関数とは
waitFor関数とは
👉getByXxx / findByXxx / queryByXxx の使い分け
こちらの記事が参考になったので引用させていただく。
get 〇〇、query 〇〇、find 〇〇では、該当する要素がある時とない時で Error を返すかどうかが違う。
👉fireEvent VS @testing-library/user-event
RTLの公式ドキュメント(Firing Events)では、下記のように@testing-library/user-eventを使ったほうがいいでと書いてある。
Most projects have a few use cases for fireEvent, but the majority of the time you should probably use @testing-library/user-event.
理由について分かりやすく説明してくれている人が既にいるので、分かりやすかったものを引用させていただく。React Testing Library では fireEvent よりも userEvent を使ったほうがいいらしい
RTL vs Enzyme
これについても沢山の人が議論してくれているので、分かりやすかったものを引用させていただく。
✅Enzyme -> コンポーネント内部のstateや子コンポーネントをMockすることができる。
✅RTL -> Enzymeのようにコンポーネント内部はいじれない。Userが実際に操作するのと同じようなテストを書く。
From Difference between enzyme, ReactTestUtils and react-testing-library
Enzyme allows you to access the internal workings of your components. You can read and set the state, and you can mock children to make tests run faster.
On the other hand, react-testing-library doesn't give you any access to the implementation details. It renders the components and provides utility methods to interact with them. The idea is that you should communicate with your application in the same way a user would. So rather than set the state of a component you reproduce the actions a user would do to reach that state.
From EnzymeよりReact Testing Libraryでしょ〜
EnzymeよりReact Testing Libraryを使うべき理由は大きくまとめて以下2つです。
・アプリケーションの効率的なテスト手法であるTesting Trophyに則ったIntegrationテストに特化したテストツールであること
・実装の詳細に依存しないテストを書くことで開発者のストレス軽減につながること
そしてEnzymeはReact17、18を公式にはサポートしていないので、そちらを踏まえても現環境ではReact Testing Libraryを使うべきと言えると思います。
Discussion