React のテストと Implementation Details

Implementation Details とは

ひさしぶりに React を書いていてテストを書きたくなった。現在は Testing Library の使用が推奨されているらしい

testing-library.com

Testing Library の Doc をみると、

You want to write maintainable tests for your React components. As a part of this goal, you want your tests to avoid including implementation details of your components and rather focus on making your tests give you the confidence for which they are intended.
https://testing-library.com/docs/react-testing-library/intro

とある。 React コンポーネントをテストするときは Implementation Details をテストしないようなテストを書くといいとのこと。

Implementation Details とはなにか、 Implementation Details をテストするテストはなぜイケてないかは以下のブログ記事が詳しい

kentcdodds.com

Implementation Details とは、例えば State のプロパティ名や onClick にわたす関数名などがある。 React Component のリファクタをするときに、これらのプロパティ名や関数名は変わるときがある。しかし、 enzyme というテストフレームワークでは実装の詳細についてテストする書きっぷりになっているので、リファクタリングするたびにテストが壊れてつらいらしい。例えば、 enzyme のテストコードでは、 State を変更するために

  const wrapper = mount(<Accordion items={[]} />)
  expect(wrapper.state('openIndex')).toBe(0)

のように直接 state を変更する必要がある。仮に openIndex というプロパティ名を変えようとすると、テストコードの修正もつらい。

Testing Library では、 直接 State を変更するのではなく、ユーザーの操作をシミュレートすることで State を変更する。例えば openIndex を操作するためにはユーザーはボタンをクリックするはず。 Testing Library では

  render(<Accordion items={} />)
  userEvent.click(screen.getByText("next"))

のようにしてユーザー目線で操作する。こうすることで、 State の内部設計 Implementation Details に踏み込むことなくテストコードを記述できる

React 特有の事情

React のテストコードで Implementation Details に特に注意しなければならない理由に、 React 特有の事情がありそうだ。 React Component のユーザーは、開発者とエンドユーザーがいる。

例えば Rust でなにかライブラリを作ることを考える。このライブラリの利用者は開発者である。公開する struct や 関数名を制限することで、あとからリファクタリングがしやすくなる。この公開インターフェイスに関してテストコードが書かれていると安心できるだろう。

React Component のユーザーは開発者とエンドユーザーである。 開発者にとっての公開インターフェイス props の名前やコールバック関数名になる。 一方で、エンドユーザーにとっては、ドロップダウンメニューのテキストやボタンがインターフェイスとなる。 React は最終的にはエンドユーザーにむけてサービス提供するので、テストもユーザーと同じ目線になって記述すべしということだ。

Testing Library の Query をみると、このあたりの思想がよく現れている。

testing-library.com

開発者としてはついつい document.getElementByIddocument.querySelector が使いたくなるが、 Testing Library ではテキストの中身や Role を指定して UI コンポーネントを絞る必要がある