Reactコンポーネントのテスト設計と実装(前編)

## はじめに

この記事ではReactコンポーネントのテストユーティリティであるReact Testing Libraryについて説明します。



**この記事で説明すること**


この記事では以下の内容について記載しています。

- 前編(この記事)
  - Testing Trophyの概要
  - React Testing Libraryのコンセプト
- [後編](https://qiita.com/takahori/items/fe5481247a1a70641c90)
  - Reactコンポーネントのテストケースの設計
  - React Testing Libraryを使用したテストコードの実装

**開発環境**

- React: 16.12.0
- Jest: 24.9.0
- React Testing Library: 9.3.2



**React Testing Libraryを使ってみようと思ったきっかけ**

だいぶ前のことになるのですが、ReactコンポーネントのテストユーティリティとしてEnzymeに代えてReact Testing Libraryを使ってみました。

Enzymeを使っていたときは、`find(#id)`や`find(displayName)`といったコードをよく書いていました。ただこのようなコードを書くと、id名やdisplayNameを変更するというリファクタリングをしただけでもテストコードを書き直す必要が出てきてしまいます。

これではリファクタリングのコストが大きすぎるということで、Enzymeで良い方法が無いかを探していたところ、むしろEnzymeに代わるものとしてReact Testing Libraryというライブラリがあることを知りましたのでこのライブラリを使用してみました。合わせて、React Testing Libraryの作者によりTesting Trophyという考え方が提唱されているということも知りました。

そこで、この記事では私がReact Testing Libraryを学習する中で知ったTesting TrophyとReact Testing Libraryの考え方と、この考え方を実際のテストコードに適用した例を説明します。




## Testing Trophyの概要
#### Testing Trophyの説明
Testing Trophyとは、自動テストにおけるコスト・実行にかかる時間・効果の大きさを表したものです。
![](https://testingjavascript.com/static/trophyWithLabels@2x-3c2b593913ddfea970b801e67648092d.png)
引用: https://testingjavascript.com



この図で表していることは以下の内容になります。

- トロフィーの各テストの部分の大きさはテストをする際にどのテストにより集中すべきかということを表しています。
 > The size of these forms of testing on the trophy is relative to the amount of focus you should give them when testing your applications (in general)

- トロフィーの上に行くほど、コスト増・実行時間がかかる・効果が大きいということを表しています
  > As you move up the testing trophy, the tests become more costly.
  > As you move up the testing trophy, the tests typically run slower
  > As you move up the testing trophy, you're increasing what I call the "confidence coefficient."



#### 自動テストの方針
上記のTesting Trophyの図に基づけば、Integration testにより集中すべきということになります。
> Integration tests strike a great balance on the trade-offs between confidence and speed/expense. This is why it's advisable to spend *most* (not all, mind you) of your effort there.



Integration testにより集中すべき理由としては以下の点になります。

- コスト/スピードと信頼度のバランスが良いから

  > Integration tests strike a great balance on the trade-offs between confidence and speed/expense

- Integration testを実施することで、多くの場合はUnit testで個別にモジュールをテストすることが不要になるため

  > So while having some unit tests to verify these pieces work in isolation isn't a bad thing, it doesn't do you any good if you don't also verify that they work together properly. And you'll find that by testing that they work together properly, you often don't need to bother testing them in isolation.





## React Testing Libraryのコンセプト

#### React Testing Libraryの特徴

React Testing Libraryの特徴としてはimplementation detailsをテストするための機能がないという点です。implementation detailsというのは、ユーザーが見たり使用したりしないものです。

>A big feature of this library is that it doesn't have utilities that enable testing implementation details. Implementation details are things which users of your code will not typically use, see, or even know about.

具体的には、CSSセレクタ、ID、displayNameなどを扱う機能がありません。

※ちなみにここに書いてあるユーザーというのは、アプリケーションの利用者(エンドユーザー)とモジュールの利用者(開発者)になります。



#### なぜimplementatin detailsのテストをすることがだめなのか

implementation detailsのテストを避けるべき理由としては以下の2点が述べられています。

- リファクタリングをしたときにテストコードが壊れてしまう
- アプリケーションのコードが正しくないときにテストに失敗せずパスしてしまうことがある

> There are two distinct reasons that it's important to avoid testing implementation details. Tests which test implementation details:
>
> 1.Can break when you refactor application code. False negatives
>
> 2.May not fail when you break application code. False positives

False negativesとFalse positivesの具体例はコードを見ていただくのが早いかと思います。以下のサイトに書いてあります。

[Testing Implementation Details](https://kentcdodds.com/blog/testing-implementation-details)



#### React Testing Libraryでのテスト

では、React Testing Libraryでどのようにテストを実施するかについては以下のように書かれています。

- ユーザー(アプリケーションの利用者/モジュールの利用者)が見るものが正しいかをテストする。つまり、コンポーネントであれば、入力されたpropsやイベントに対して、正しい内容がレンダリングされるかをテストする

  > our test should typically only see/interact with the props that are passed, and the rendered output.

- 実際のDOMを操作してテストする

  > So rather than dealing with instances of rendered react components, your tests will work with actual DOM nodes.

  > Often in tests using enzyme, to find the "Load Greeting" button you might use a CSS selector or even find by component `displayName` or the component constructor. But when the user wants to load the greeting, they don't care about those implementation details, instead they're going to find and click the button that says "Load Greeting." And that's exactly what our test is doing with the `getByText` helper!

2点目は、つまり、画面に実際に表示されている内容を参照してテストを実行する際の要素を取得するということを表しています。

これを実現するために`getByLabelText`・`getByText`・`getByAltText`といったAPIが提供されています。


#### 補足：Enzymeとの比較

React Testing Libraryの作者が述べているようにEnzymeでもReact Testing Libraryと同じようなテストをすることはできます。ただし、implementation detailsのテストをしないことを強制するためにReact Testing Libraryを使用したほうがよいようです。

> So we could rewrite all these tests with enzyme, limiting ourselves to APIs that are free of implementation details, but instead, I'm just going to use React Testing Library which will make it very difficult to include implementation details in my tests.

Enzymeでは、`setState`・`state`・`find(#selector/displayName)`などのAPIが提供されていますが、これらはimplementation detailsにあたりますので、React Testing Libraryではこれらと同様の機能をもつAPIは提供されていません。



## 参考にしたサイト
**Testing Trophyについて**
[Write tests. Not too many. Mostly integration.](https://kentcdodds.com/blog/write-tests)
[Static vs Unit vs Integration vs E2E Testing for Frontend Apps](https://kentcdodds.com/blog/unit-vs-integration-vs-e2e-tests)

**React Testing Libraryについて**
[Introducing the react-testing-library](https://kentcdodds.com/blog/introducing-the-react-testing-library)
[Testing Implementation Details](https://kentcdodds.com/blog/testing-implementation-details)
[Making your UI tests resilient to change](https://kentcdodds.com/blog/making-your-ui-tests-resilient-to-change)

**APIリファレンス**
[React Testing Library](https://testing-library.com/docs/dom-testing-library/api-queries)
[jest-dom](https://github.com/testing-library/jest-dom#readme)