Enzyme からの移行
このページは、Enzyme の経験があり、React Testing Library への移行を理解しようとしている開発者を対象としています。すべての種類のテストの移行について詳しく説明するものではありませんが、Enzyme と React Testing Library を比較している人にとっては役立つ情報が含まれています。
React Testing Library とは?
React Testing Library は、Testing Library という名前のオープンソースプロジェクトの一部です。Testing Library プロジェクトには、より簡潔で有用なテストを作成するために使用できる、他のいくつかの便利なツールとライブラリがあります。React Testing Library の他に、プロジェクトの他のライブラリには、以下のようなものがあり、役立つでしょう。
@testing-library/jest-dom:
jest-dom
は、Jest を拡張するために使用できるカスタム Jest マッチャーのセットを提供します。これにより、テストがより宣言的になり、読みやすくなり、メンテナンスが容易になります。@testing-library/user-event:
user-event
は、ユーザーがページ上の要素と対話するときにブラウザで発生する実際のイベントをシミュレートしようとします。たとえば、userEvent.click(checkbox)
は、チェックボックスの状態を変更します。
React Testing Library を使用する理由
Enzyme は強力なテストライブラリであり、その貢献者は JavaScript コミュニティに多くの貢献をしてきました。実際、React Testing Library のメンテナーの多くは、React Testing Library を開発して取り組む前に、長年 Enzyme を使用し、貢献してきました。そのため、Enzyme の貢献者に感謝の意を表したいと思います!
React Testing Library の主な目的は、ユーザーが使用する方法でコンポーネントをテストすることにより、テストへの信頼を高めることです。ユーザーは、舞台裏で何が起こっているかを気にせず、出力を見て操作するだけです。コンポーネントの内部 API にアクセスしたり、state
を評価したりする代わりに、コンポーネントの出力に基づいてテストを作成することで、より自信が得られます。
React Testing Library は、多くの開発者が Enzyme でテストを作成する際に直面する問題を解決することを目指しています。Enzyme は、開発者が実装の詳細をテストすることを許可し(そして推奨し)ています。このようにテストを行うと、最終的に、テストを変更せずにコンポーネントを変更およびリファクタリングできなくなります。結果として、テストは開発速度と生産性を低下させます。小さな変更でも、コンポーネントの出力に影響を与えない場合でも、テストの一部を書き換える必要がある場合があります。
React Testing Library でテストを書き直すことは、長期的に見て、あなたの足を引っ張るテストを、より自信を与え、生産性を向上させるテストに交換することになるため、価値があります。
Enzyme から React Testing Library への移行方法
移行を成功させるために、同じアプリケーション内で 2 つのテストライブラリを並行して実行し、Enzyme テストを React Testing Library に 1 つずつ移植することで、段階的に移行することをお勧めします。これにより、大規模で複雑なアプリケーションでも、他の業務を中断することなく移行することが可能になります。なぜなら、作業は共同で行うことができ、時間をかけて分散させることができるからです。
React Testing Library のインストール
まず、React Testing Library と jest-dom
ヘルパーライブラリをインストールします(完全なインストールとセットアップガイドについては、このページを確認してください)。
- npm
- Yarn
npm install --save-dev @testing-library/react @testing-library/jest-dom
yarn add --dev @testing-library/react @testing-library/jest-dom
テストへの React Testing Library のインポート
Jest を使用している場合は(他のテストフレームワークも使用できます)、テストファイルに次のモジュールをインポートするだけで済みます。
// import React so you can use JSX (React.createElement) in your test
import React from 'react'
/**
* render: lets us render the component as React would
* screen: a utility for finding elements the same way the user does
*/
import {render, screen} from '@testing-library/react'
テスト構造は、Enzyme で記述する場合と同じにすることができます。
test('test title', () => {
// Your tests come here...
})
注:React Testing Library では、
describe
ブロックとit
ブロックも使用できます。React Testing Library は、Jest を置き換えるものではなく、Enzyme を置き換えるだけです。test
を推奨するのは、テスト時のネストを避けるのに役立つためです。
Enzyme から React Testing Library への基本的な移行例
1 つ注意すべきことは、Enzyme の機能と React Testing Library の機能に 1 対 1 のマッピングがないということです。Enzyme の多くの機能は、いずれにしても非効率なテストになるため、Enzyme で慣れている機能の一部は残す必要があります(wrapper
変数や wrapper.update()
呼び出しなどは必要なくなります)。
React Testing Library には、コンポーネントの要素とそのプロパティにアクセスできる便利なクエリがあります。ここでは、一般的な Enzyme テストと、React Testing Library を使用した代替案を示します。
挨拶メッセージを表示する Welcome
コンポーネントがあるとします。このコンポーネントをテストする方法を学ぶために、Enzyme テストと React Testing Library テストの両方を見てみましょう。
React コンポーネント
次のコンポーネントは、props
から name
を取得し、h1
要素に挨拶メッセージを表示します。また、ユーザーが別の名前に変更できるテキスト入力もあり、それに応じてテンプレートが更新されます。CodeSandbox でライブバージョンを確認してください。
const Welcome = props => {
const [values, setValues] = useState({
firstName: props.firstName,
lastName: props.lastName,
})
const handleChange = event => {
setValues({...values, [event.target.name]: event.target.value})
}
return (
<div>
<h1>
Welcome, {values.firstName} {values.lastName}
</h1>
<form name="userName">
<label>
First Name
<input
value={values.firstName}
name="firstName"
onChange={handleChange}
/>
</label>
<label>
Last Name
<input
value={values.lastName}
name="lastName"
onChange={handleChange}
/>
</label>
</form>
</div>
)
}
export default Welcome
テスト 1: コンポーネントをレンダリングし、h1
の値が正しいか確認する
Enzyme テスト
test('has correct welcome text', () => {
const wrapper = shallow(<Welcome firstName="John" lastName="Doe" />)
expect(wrapper.find('h1').text()).toEqual('Welcome, John Doe')
})
React Testing Library
test('has correct welcome text', () => {
render(<Welcome firstName="John" lastName="Doe" />)
expect(screen.getByRole('heading')).toHaveTextContent('Welcome, John Doe')
})
ご覧のとおり、テストは非常によく似ています。Enzyme の shallow
レンダラーはサブコンポーネントをレンダリングしないため、React Testing Library の render
メソッドは、Enzyme の mount
メソッドに似ています。
React Testing Library では、render
の結果を変数(つまり、wrapper
)に割り当てる必要はありません。screen
オブジェクトで関数を呼び出すだけで、レンダリングされた出力にアクセスできます。もう 1 つ知っておくべき良い点は、React Testing Library が各テスト後に環境を自動的にクリーンアップするため、afterEach
関数または beforeEach
関数で cleanup
を呼び出す必要がないことです。
もう 1 つ気づくかもしれないことは、引数として 'heading'
を持つ getByRole
です。'heading'
は、h1
要素のアクセス可能なロールです。それらの詳細については、クエリのドキュメントページで学ぶことができます。Testing Library について人々がすぐに気に入る点の 1 つは、よりアクセシブルなアプリケーションを書くように促してくれる点です(アクセシブルでない場合、テストが難しくなるため)。
テスト 2: 入力テキストに正しい値があることを確認する
上記のコンポーネントでは、入力値は props.firstName
値と props.lastName
値で初期化されます。値が正しいかどうかを確認する必要があります。
Enzyme
test('has correct input value', () => {
const wrapper = shallow(<Welcome firstName="John" lastName="Doe" />)
expect(wrapper.find('input[name="firstName"]').value).toEqual('John')
expect(wrapper.find('input[name="lastName"]').value).toEqual('Doe')
})
React Testing Library
test('has correct input value', () => {
render(<Welcome firstName="John" lastName="Doe" />)
expect(screen.getByRole('form')).toHaveFormValues({
firstName: 'John',
lastName: 'Doe',
})
})
素晴らしい!非常にシンプルで便利であり、テストは非常に明確であるため、あまり詳しく説明する必要はありません。気づくかもしれないことの 1 つは、<form>
に role="form"
属性がありますが、これは何ですか?
role
は、障害を持つ人々向けに Web アプリケーションを改善するために使用することが推奨される、アクセシビリティ関連の属性の 1 つです。一部の要素にはデフォルトの role
値があり、それらに対して設定する必要はありませんが、<div>
のような一部の要素にはデフォルトの role
値がありません。<div>
要素にアクセスするにはさまざまなアプローチを使用できますが、コンポーネントが障害を持つ人々やスクリーンリーダーを使用する人々がアクセスできるようにするために、暗黙的な role
で要素にアクセスすることをお勧めします。クエリドキュメントのこのセクションは、概念をよりよく理解するのに役立つ可能性があります。
<form>
要素は、'form'
の暗黙的なrole
を持つために、name
属性を持っている必要があります(仕様で要求されているとおり)。
React Testing Library は、ユーザーがコンポーネントを使用する方法をテストすることを目指しています。ユーザーは、ボタン、見出し、フォーム、その他の要素を、id
、class
、または要素タグ名ではなく、それらのロールによって認識します。したがって、React Testing Library を使用する場合は、document.querySelector
API を使用して DOM にアクセスすることを避ける必要があります。(テストで使用することはできますが、この段落で述べた理由からお勧めしません。)
React Testing Library は、コンポーネント要素に効率的にアクセスするのに役立つ便利なクエリ API をいくつか公開しています。利用可能なクエリのリストはこちらで確認できます。特定の状況でどのクエリを使用すべきかわからない場合は、どのクエリを使用すべきかを説明した素晴らしいページがありますので、ぜひご覧ください。
React Testing Library のどのクエリを使用すべきかについてまだ疑問がある場合は、testing-playground.com と、それに付随する Chrome 拡張機能のTesting Playgroundをご覧ください。これは、開発者がテストを書く際に最適なクエリを見つけることを目的としています。また、要素を選択するための最適なクエリを見つけるのにも役立ちます。Chrome Developer Tools で要素の階層を検査し、それらを選択する方法に関する提案を提供し、同時に優れたテストの実践を推奨します。
act() と wrapper.update() の使用
Enzyme で非同期コードをテストする場合、通常、テストを正しく実行するために act()
を呼び出す必要があります。React Testing Library を使用する場合、API 呼び出しがデフォルトで act()
でラップされるため、ほとんどの場合、明示的に act()
を呼び出す必要はありません。
update()
は Enzyme のコンポーネントツリーのスナップショットを React のコンポーネントツリーと同期するため、Enzyme のテストで wrapper.update()
を見ることがあります。React Testing Library には同様のメソッドはなく(また必要もありません)。これは、対処する必要のあることが少ないため、あなたにとって良いことです!
ユーザーイベントのシミュレート
React Testing Library でユーザーイベントをシミュレートするには 2 つの方法があります。1 つは user-event
ライブラリを使用する方法で、もう 1 つは React Testing Library に含まれている fireEvent
を使用する方法です。user-event
は実際には fireEvent
の上に構築されています(fireEvent
は、指定された要素に対して単純に dispatchEvent
を呼び出します)。user-event
は、一般的なユーザーインタラクションにおいてすべてのイベントが正しい順序で発生するようにするため、一般的に推奨されています。これにより、テストが実際にソフトウェアが使用される方法に似たものになることが保証されます。
@testing-library/user-event
モジュールを使用するには、まずインストールします
- npm
- Yarn
npm install --save-dev @testing-library/user-event @testing-library/dom
yarn add --dev @testing-library/user-event @testing-library/dom
これで、テストにインポートできます
import userEvent from '@testing-library/user-event'
user-event
ライブラリの使用方法を説明するために、チェックボックス入力と関連付けられたラベルを表示する Checkbox
コンポーネントがあると想像してください。チェックボックスをクリックするユーザーのイベントをシミュレートしたいと思います。
import React from 'react'
const Checkbox = () => {
return (
<div>
<label htmlFor="checkbox">Check</label>
<input id="checkbox" type="checkbox" />
</div>
)
}
export default Checkbox
ユーザーがチェックボックスの関連ラベルをクリックしたときに、入力の "checked" プロパティが正しく設定されていることをテストしたいと思います。その場合のテストをどのように書くか見てみましょう。
test('handles click correctly', async () => {
render(<Checkbox />)
const user = userEvent.setup()
// You can also call this method directly on userEvent,
// but using the methods from `.setup()` is recommended.
await user.click(screen.getByText('Check'))
expect(screen.getByLabelText('Check')).toBeChecked()
})
素晴らしい!
テストでのクラスメソッドのトリガー(wrapper.instance()
)
すでに説明したように、実装の詳細やユーザーが認識しないことをテストしないことをお勧めします。ユーザーが使用するのと同じように、コンポーネントをテストして操作することを目指します。
テストで次を使用している場合`instance()`または`state()`、ユーザーが知り得ない、または気にもかけないことをテストしていることを知ってください。これにより、ユーザーがそれらを使用するときに物事がうまくいくという確信を得ることからテストが遠ざかるでしょう。— Kent C. Dodds
コンポーネントの内部をテストする方法がわからない場合は、一歩下がって、「ユーザーがこのコードを実行するために何をしますか?」と考えてください。そして、あなたのテストでそれを行いましょう。
コンポーネントを shallow
レンダリングするには?
一般的に、コンポーネントをモックアウトすることは避けるべきです。ただし、必要な場合は、Jest のモック機能を使用してみてください。詳細については、FAQ を参照してください。