統合テストは、テストのコストと価値の間のスイートスポットです。コンポーネントユニットテストの代わりに、またはコンポーネントユニットテストに加えて、react-testing-libraryを使用してReactアプリの統合テストを作成すると、開発速度を損なうことなくコードの保守性を向上させることができます。
先に進む前に有利なスタートを切りたい場合は、Reactアプリ統合テストにreact-testing-libraryを使用する方法の例をご覧ください。 ここに 。
「統合テストは、信頼性と速度/費用の間のトレードオフに大きなバランスを取ります。これが、あなたの努力の大部分(すべてではありませんが、気に留めてください)をそこで過ごすことが賢明な理由です。
– Kent C. Dodds in テストを書く。あまり多くはありません。主に統合。
Reactコンポーネントの単体テストを作成するのが一般的な方法であり、Reactの「酵素」をテストするために一般的なライブラリを使用することがよくあります。具体的には、その「浅い」方法です。このアプローチにより、アプリの他の部分から分離してコンポーネントをテストできます。ただし、Reactアプリの作成はコンポーネントの作成がすべてであるため、単体テストだけではアプリにバグがないことを保証できません。
たとえば、コンポーネントの受け入れられた小道具を変更し、関連する単体テストを更新すると、すべてのテストに合格する可能性がありますが、別のコンポーネントがそれに応じて更新されなかった場合、アプリはまだ壊れている可能性があります。
統合テストは、コンポーネントの構成が目的のUXをもたらすことを保証するため、Reactアプリに変更を加える際の安心感を維持するのに役立ちます。
ここにいくつかのものがあります React開発者 欲しいです 統合テストを作成するときに行うこと:
さて、いくつかのことのために私たちはしようとすべきです 避ける Reactアプリ統合テストを作成する場合:
前述の要件により react-testing-library その主な指針となる原則は、Reactコンポーネントを実際の人間がどのように使用するかに似た方法でテストできるようにすることであるため、優れた選択肢です。
ライブラリとそのオプション コンパニオンライブラリ 、DOMと相互作用し、その状態をアサートするテストを作成できます。
サンプル統合テストを作成するアプリは、単純なシナリオを実装します。
上記の機能がどのように実装されるかは、統合テストの観点からは無関係である必要があります。ただし、実際のアプリケーションに近づけるために、アプリは一般的なReactパターンに従います。したがって、アプリは次のようになります。
アプリ実装のソースコードは次のとおりです。 ここに 。
市場戦略フレームワークに行く
糸付き:
yarn add --dev jest @testing-library/react @testing-library/user-event jest-dom nock
またはnpmで:
npm i -D jest @testing-library/react @testing-library/user-event jest-dom nock
viewGitHubRepositoriesByUsername.spec.js
という名前のファイルを作成します./test
内のファイルアプリケーションのフォルダ。ジェストは自動的にそれを拾います。
import React from 'react'; // so that we can use JSX syntax import { render, cleanup, waitForElement } from '@testing-library/react'; // testing helpers import userEvent from '@testing-library/user-event' // testing helpers for imitating user events import 'jest-dom/extend-expect'; // to extend Jest's expect with DOM assertions import nock from 'nock'; // to mock github API import { FAKE_USERNAME_WITH_REPOS, FAKE_USERNAME_WITHOUT_REPOS, FAKE_BAD_USERNAME, REPOS_LIST } from './fixtures/github'; // test data to use in a mock API import './helpers/initTestLocalization'; // to configure i18n for tests import App from '../App'; // the app that we are going to test
describe('view GitHub repositories by username', () => { beforeAll(() => { nock('https://api.github.com') .persist() .get(`/users/${FAKE_USERNAME_WITH_REPOS}/repos`) .query(true) .reply(200, REPOS_LIST); }); afterEach(cleanup); describe('when GitHub user has public repositories', () => { it('user can view the list of public repositories for entered GitHub username', async () => { // arrange // act // assert }); }); describe('when GitHub user has no public repositories', () => { it('user is presented with a message that there are no public repositories for entered GitHub username', async () => { // arrange // act // assert }); }); describe('when GitHub user does not exist', () => { it('user is presented with an error message', async () => { // arrange // act // assert }); }); });
ノート:
describe
ブロックは、統合テストのユースケースとフローのバリエーションを指定します。it
ブロックは、テストしているユースケースに非同期ステップがあるため、非同期コールバックを使用します。まず、アプリをレンダリングする必要があります。
const { getByText, getByPlaceholderText, queryByText } = render();
render
@testing-library/react
からインポートされたメソッドモジュールは、テストReact DOMでアプリをレンダリングし、レンダリングされたアプリコンテナーにバインドされたDOMクエリを返します。これらのクエリは、対話およびアサートするDOM要素を見つけるために使用されます。
ここで、テスト対象のフローの最初のステップとして、ユーザーにユーザー名フィールドが表示され、ユーザー名文字列が入力されます。
userEvent.type(getByPlaceholderText('userSelection.usernamePlaceholder'), FAKE_USERNAME_WITH_REPOS);
userEvent
インポートされた@testing-library/user-event
からのヘルパーモジュールにはtype
がありますユーザーがテキストフィールドにテキストを入力するときのユーザーの動作を模倣するメソッド。入力を受け入れるDOM要素とユーザーが入力する文字列の2つのパラメーターを受け入れます。
ユーザーは通常、それらに関連付けられたテキストによってDOM要素を見つけます。入力の場合は、ラベルテキストまたはプレースホルダーテキストのいずれかです。 getByPlaceholderText
render
から以前に返されたクエリメソッドプレースホルダーテキストでDOM要素を見つけることができます。
テキスト自体は変更される可能性が高いため、実際のローカリゼーション値に依存せず、代わりにローカリゼーションアイテムキーを値として返すようにローカリゼーションモジュールを構成することをお勧めします。
たとえば、「en-US」ローカリゼーションが通常Enter GitHub username
を返す場合userSelection.usernamePlaceholder
の値としてキー、テストでは、userSelection.usernamePlaceholder
を返すようにします。
nodejs静的サイトジェネレーター
ユーザーがフィールドにテキストを入力すると、テキストフィールドの値が更新されていることがわかります。
expect(getByPlaceholderText('userSelection.usernamePlaceholder')).toHaveAttribute('value', FAKE_USERNAME_WITH_REPOS);
次にフローで、ユーザーは送信ボタンをクリックし、リポジトリのリストが表示されることを期待します。
userEvent.click(getByText('userSelection.submitButtonText').closest('button')); getByText('repositories.header');
userEvent.click
メソッドは、ユーザーがDOM要素をクリックするのを模倣しますが、getByText
クエリは、含まれているテキストによってDOM要素を検索します。 closest
修飾子は、正しい種類の要素を選択することを保証します。
注意: 統合テストでは、多くの場合、ステップは両方に役立ちますact
およびassert
役割。たとえば、ユーザーがボタンをクリックすることでボタンをクリックできることを表明します。
前の手順で、ユーザーにアプリのリポジトリリストセクションが表示されることを確認しました。ここで、GitHubからリポジトリのリストをフェッチするのに時間がかかる可能性があるため、ユーザーにはフェッチが進行中であることが示されていることを確認する必要があります。また、リポジトリリストがまだフェッチされている間、入力されたユーザー名に関連付けられたリポジトリがないことをアプリがユーザーに通知しないようにする必要があります。
getByText('repositories.loadingText'); expect(queryByText('repositories.empty')).toBeNull();
getBy
に注意してくださいクエリプレフィックスは、DOM要素が見つかり、queryBy
であることを表明するために使用されます。クエリプレフィックスは、反対のアサーションに役立ちます。また、queryBy
要素が見つからない場合、エラーを返しません。
次に、最終的に、アプリがリポジトリのフェッチを終了し、それらをユーザーに表示することを確認する必要があります。
await waitForElement(() => REPOS_LIST.reduce((elementsToWaitFor, repository) => { elementsToWaitFor.push(getByText(repository.name)); elementsToWaitFor.push(getByText(repository.description)); return elementsToWaitFor; }, []));
waitForElement
非同期メソッドは、メソッドパラメーターとして提供されたアサーションをtrueにするDOM更新を待機するために使用されます。この場合、モックされたGitHubAPIによって返されるすべてのリポジトリの名前と説明がアプリに表示されることを表明します。
最後に、アプリはリポジトリがフェッチされていることを示すインジケーターを表示しなくなり、エラーメッセージも表示されなくなります。
expect(queryByText('repositories.loadingText')).toBeNull(); expect(queryByText('repositories.error')).toBeNull();
結果のReact統合テストは次のようになります。
it('user can view the list of public repositories for entered GitHub username', async () => { const { getByText, getByPlaceholderText, queryByText } = render(); userEvent.type(getByPlaceholderText('userSelection.usernamePlaceholder'), FAKE_USERNAME_WITH_REPOS); expect(getByPlaceholderText('userSelection.usernamePlaceholder')).toHaveAttribute('value', FAKE_USERNAME_WITH_REPOS); userEvent.click(getByText('userSelection.submitButtonText').closest('button')); getByText('repositories.header'); getByText('repositories.loadingText'); expect(queryByText('repositories.empty')).toBeNull(); await waitForElement(() => REPOS_LIST.reduce((elementsToWaitFor, repository) => { elementsToWaitFor.push(getByText(repository.name)); elementsToWaitFor.push(getByText(repository.description)); return elementsToWaitFor; }, [])); expect(queryByText('repositories.loadingText')).toBeNull(); expect(queryByText('repositories.error')).toBeNull(); });
ユーザーがパブリックリポジトリが関連付けられていないGitHubユーザー名を入力すると、アプリは適切なメッセージを表示します。
describe('when GitHub user has no public repositories', () => { it('user is presented with a message that there are no public repositories for entered GitHub username', async () => { const { getByText, getByPlaceholderText, queryByText } = render(); userEvent.type(getByPlaceholderText('userSelection.usernamePlaceholder'), FAKE_USERNAME_WITHOUT_REPOS); expect(getByPlaceholderText('userSelection.usernamePlaceholder')).toHaveAttribute('value', FAKE_USERNAME_WITHOUT_REPOS); userEvent.click(getByText('userSelection.submitButtonText').closest('button')); getByText('repositories.header'); getByText('repositories.loadingText'); expect(queryByText('repositories.empty')).toBeNull(); await waitForElement(() => getByText('repositories.empty')); expect(queryByText('repositories.error')).toBeNull(); }); });
ユーザーが存在しないGitHubユーザー名を入力すると、アプリはエラーメッセージを表示します。
describe('when GitHub user does not exist', () => { it('user is presented with an error message', async () => { const { getByText, getByPlaceholderText, queryByText } = render(); userEvent.type(getByPlaceholderText('userSelection.usernamePlaceholder'), FAKE_BAD_USERNAME); expect(getByPlaceholderText('userSelection.usernamePlaceholder')).toHaveAttribute('value', FAKE_BAD_USERNAME); userEvent.click(getByText('userSelection.submitButtonText').closest('button')); getByText('repositories.header'); getByText('repositories.loadingText'); expect(queryByText('repositories.empty')).toBeNull(); await waitForElement(() => getByText('repositories.error')); expect(queryByText('repositories.empty')).toBeNull(); }); });
統合テストは、Reactアプリケーションに最適な場所です。これらのテストは、バグを見つけて使用するのに役立ちます TDDアプローチ 同時に、実装が変更されたときにメンテナンスを必要としません。
この記事で紹介するReact-testing-libraryは、ユーザーと同じようにアプリを操作し、ユーザーの視点からアプリの状態と動作を検証できるため、React統合テストを作成するための優れたツールです。
ここで提供されている例が、新規および既存のReactプロジェクトの統合テストの作成を開始するのに役立つことを願っています。アプリの実装を含む完全なサンプルコードは、私の GitHub 。
Reactライブラリで記述されたユーザーインターフェイスの自動テスト。
統合テストは、システムの個別のコンポーネントが連携してユーザーの目標を達成していることを確認することを目的とした自動テストの変形です。
統合テストではコンポーネントが一緒に機能することを確認し、単体テストでは各コンポーネントが独自に機能することを確認しますが、これはコンポーネントの統合に問題がないことを保証するものではありません。
エンドツーエンドテストは、すべてのシステムが連携してユーザーの目標を達成していることを確認することを目的とした自動テストの変形です。
これは、ユーザーインターフェースを構築するための優れた宣言型のコンポーネントベースのライブラリです。