Write tests. Not too many. Mostly integration. * 1
テストを書こう。多く書かなくていい。ほどんどインテグレーションテストでいい。
ReactでコンポーネントのUnit/Integrationテストをする時、現時点で多く使われているのがEnzymeとReact Testing Libraryです。公式Doc上でも両方に言及されていて、どちらか一方が圧倒的な支持を受けている訳でもないです。
この記事では、**EnzymeとReact Testing Libraryどっちにしたらいいの??**という問題に対して私なりの回答をまとめていきます。端的に言うと、単純にどちらのライブラリが使いやすいかどうか以上に、テストに対する考え方の問題だと考えています。
Enzymeで書く場合
Enzymeではshallow render機能やDOM query selectorが使えるので、コンポーネントの挙動よりもコードの実装に対するUnitテストを書きやすくなります。
以下で実際にToDoアプリのテストを書いた場合の例を見てみましょう。
ToDoアプリの<ToDoView/ >の中にToDoを追加する<AddToDoButton />があって、その機能がちゃんと動くかをテストしたいとします。
import ToDo from '../components/AddToDoButton'
import { shallow } from 'enzyme';
test('onClick called', () => {
const wrapper = shallow(<AddToDoButton />)
wrapper.instance().onClick = jest.fn()
wrapper.update()
wrapper.instance().handleClick("ポケセンに行く")
expect(wrapper.instance().onClick).calledWith("ポケセンに行く")
});
このサンプルでは、handleClickメゾットを実行した時にonClickメゾットが正しく呼ばれたかを確認しています。
React Testing Libraryで書く場合
React testing libraryで同じテストを書いた場合です。
import userEvent from '@testing-library/user-event';
import ToDoView from '../components/ToDoView'
test('can add a new todo' () => {
const { getByText, getByLabelText } = render(<ToDoView />);
const inputField = getByLabelText('ToDo追加:');
const submitButton = getByText('送信');
userEvent(inputField).type('ポケセンに行く');
userEvent(submitButton).click(submitButton);
expect(getByText('ポケセンに行く')).toBeDefined();
});
ここで使われているReact Testing Libraryの関数をおさらいします:
-
render:<body>タグの中に指定されたコンポーネントをrenderします。Enzymeで言うmountです。 -
getByText: 指定されたテキストを持つDOM nodeを検索します。
この例では、onClick関数が呼ばれたか確認するよりも、実際のinput fieldにポケセンに行くと入力して、submitButtonをクリックし、ポケセンに行くという文字を持つToDoリストがあるかを確認しています。
使ってみるとわかりますが、React Testing Libraryでコンポーネント内部のprops/stateをテストしたりコンポーネントの実装をテストするのは難しいです。そういう実際のエンドユーザーに関係の薄いコードの実装ではなく、実際にユーザーがToDoアプリを使う時のステップに基づいてテストを書かせる作りになっています。
Enzyme vs React Testing Library?
上の2つのサンプルコード比較した時、Enzymeパターンのほうが行数もステップも少ないので、早く書けるのはEnzymeの方だと言えるでしょう。
でも、本当に意味のあるテストはReact Testing Libraryで書いた方だと思います。
onClickメゾットがコールされていても、handleClickメゾットの実装が間違えていたり、<ToDoView />と<AddToDoButton />の連結がうまくいってなかったりして実際にToDoリストが追加されないケースもあります。最終的に、ToDoアプリのテストで一番確認しないといけないのは、**「onClickメゾットがコールされたか」よりも、「ToDoリストが実際に追加されたか」**という点です。
また、Enzymeパターンのテストだと<AddToDoButton />コンポーネントだけのUnitテストですが、React Testing Libraryのコードサンプルでは、実際にボタンをクリックして、ToDoリストが表示されたか確認しているので、<ToDoView />と<AddToDoButton />両方のコンポーネントのIntegrationテストです。
誤解のないように説明すると、EnzymeでもReact Testing Libraryで書いたようなBDD(Behavioral Driven)スタイルのテストは書けるし、React Testing LibraryでTDDスタイルのテストも書けます。
ただ、shallowrenderがあまりに簡単に書けるので、Enzyme使用のプロジェクトではほぼ前者のスタイルのテストが量産されます。使ってみるとわかりますが、Enzymeではコードの実装を確認するようなテストを書きがちなのに対して、React Testing Libraryではコンポーネントの実際の挙動を確認するようなテストを書くことが多くなります。
結局どっちを使えばいいの?
じゃあ結局Enzyme / React Testing Libraryどちらが良いのか?それを決める時は、「そのアプリでどういうテストを書きたいか」ということを考えてください。
Business Logicやコードの実装のテストをしたい場合はEnzymeが向いているし、実際のユーザーのアプリの使い方に基づいたテストをしたい場合はReact Testing Libraryが向いています。
また「どういうテストが書きたいか」を考える時に、「どういうテストが一番価値があるのか」ということも考えるべきです。フロントエンドのテストにはe2eテスト、Integrationテスト、Unitテストがありますが、一番大切なのはe2eテストなのか、Integrationなのか、Unitなのか?
React Testing LibraryだとコードベースにIntegrationテストが多くなりますし、EnzymeだとUnitテストが多くなります。
個人的には、ほどんどのフロントエンドプロジェクトのテストはBDD寄り、Integrationテストが多めで良いと思っているので、React Testing Libraryを推します。*2 Hooksが登場してからほどんどのコンポーネントはFunctional componentなので、そもそもクラスメゾットのUnitテストを書く必要もほどんどないし、Enzymeの必要性を感じません。
テストのライブラリの選択は、どういうテストを書くのかの選択です。それぞれのプロジェクトにとってベストな選択ができるよう願っています。
まとめ TL;DR
| Enzyme | React Testing Library | |
|---|---|---|
| TDD寄りの考え方のReactテストライブラリ | BDD寄りの考え方のReactテストライブラリ | |
| Github stars | 18.5K | 10.7K |
| Pros | * Unit テストが多くなる* shallow renderができる、shallow renderのテストは書きやすい * componentのprops/stateに直接アクセスしたりいじれたりする | * Integrationテストが多くなる* コードの実装よりも実際にユーザーに見えるコンポーネントの挙動をテストする * dom ID/classよりもエンドユーザーに実際に関係のあるText/Roleなどで要素を選択できるユーティリティが豊富 |
| Cons | * 実際にユーザーに見えない部分をチェックする意味のないテストを量産しがち | * shallow renderがない(意図的にサポートしていない) * full dom renderしか使えないので、他のコンポーネント/libraryに依存する場合はモックが大変 |
*1 https://twitter.com/rauchg/status/807626710350839808?ref_src=twsrc%5Etfw%7Ctwcamp%5Etweetembed%7Ctwterm%5E807626710350839808&ref_url=https%3A%2F%2Fkentcdodds.com%2Fblog%2Fwrite-tests%23---154-185
*2 長くなるので、理由は気が向いたら別の記事で書きます。
