JestとReact Testing Libraryで学ぶReactアプリのテスト入門:基礎から応用まで

JestとReact Testing Libraryで学ぶReactアプリのテスト入門:基礎から応用まで

今回は、JavaScriptのテストフレームワークコンポーネントをテストするためのReactユーティリティの組み合わせとして人気の高い、JestとReact Testing Libraryを使ってReactアプリケーションをテストする方法について説明します。

テストとは

テストは、アプリケーションとやり取りして、そのすべての機能と機能が意図したとおりに動作することを確認する行為またはプロセスです。

また一般に、「手動テスト」と「自動テスト」の2種類のテストがあります。

手動テスト: 手動テストは、テストを人間が手動で実行する手法です。

手動テストは、特に機能テストや受け入れテストなどの場合に利用されます。

手動テストは、自動化することが困難な場合があるため、必要に応じて利用されます。

自動テスト:自動テストは、テストを自動化することにより、テストの効率性、再現性、正確性を向上させるための手法です。

これは、テストスクリプトを自動的に実行して、アプリケーションの振る舞いを確認することで実現されます。

自動テストは、ユニットテスト結合テスト、機能テスト、パフォーマンステストなど、様々な種類のテストに利用されます。

自動テストと手動テストのどちらが優れているかは、使用するテストの種類や目的によって異なります。

自動テストは繰り返し実行することができ、手動テストよりも速く、効率的に実行できます。

一方、手動テストは、人間の目や直感を利用して、より広い範囲のテストケースをカバーすることができます。

どちらも適切に利用することで、高品質なソフトウェアの開発を支援することができます。

自動テストをリストとしてまとめると、以下のような種類があります。

ユニットテスト:個々のコンポーネントを単独でテストすることを目的としたテストです。通常は開発者によって書かれ、プログラムコードとともにバージョン管理システムに格納されます。

結合テスト:複数のコンポーネントが連携する際の振る舞いをテストする手法です。複数のユニットテストを組み合わせて実施されることがあります。

機能テスト:ソフトウェアの機能が仕様書通りに動作するかを確認するテストです。ユーザビリティやユーザエクスペリエンスなどの側面も含まれます。

パフォーマンステスト:ソフトウェアのパフォーマンスを評価するテストです。負荷テスト、ストレステスト、容量テストなどの種類があります。

セキュリティテスト:ソフトウェアの脆弱性を検出するテストです。侵入テスト、スキャンテスト、脆弱性評価などの種類があります。

可用性テスト:ソフトウェアが適切に利用できるかを確認するテストです。利便性、アクセシビリティ、親和性などの側面も含まれます。 インテグレーションテスト:異なるシステム、モジュール、コンポーネントが統合された際の動作を確認するテストです。

これらの種類の自動テストを組み合わせて、開発プロセス全体でソフトウェアの品質を確保することができます。

また、手動テストには、以下のような種類があります。

機能テスト:ソフトウェアの機能が仕様書通りに動作するかを確認するテストです。

機能テストには、正常系テストや異常系テストが含まれます。

受け入れテスト:ソフトウェアがユーザーの要件に合致するかを確認するテストです。

ユーザーが望む機能や要件が満たされているかを確認します。

手順テスト:ソフトウェアの操作手順が正しいかを確認するテストです。

ユーザーがどのようにソフトウェアを操作するかを確認します。

互換性テスト:ソフトウェアが異なる環境やプラットフォームで正常に動作するかを確認するテストです。

異なるOS、ブラウザ、デバイスなどでテストを行います。

ユーザビリティテスト:ソフトウェアが利用しやすいかを確認するテストです。

ユーザーが操作しやすく、直感的であるかを確認します。

ブラックボックステスト:内部構造を知らずに、外部の入力と出力をテストする手法です。

一般的に機能テストに使用されます。

グレーボックステスト:内部構造をある程度知っている状態で、テストを実施する手法です。

結合テストやインテグレーションテストなどに使用されます。

これらの手動テストは、自動テストと組み合わせて、開発プロセス全体でソフトウェアの品質を確保することができます。

本日は、自動テストのユニットテストおよび結合テストの解説となりますので、こちら重点的に詳しく解説致します。

ユニットテスト結合テスト

ユニットテスト

ユニットテスト(同義語:単体テスト)では、個々のソフトウェアユニットまたはコンポーネントを分離してテストします。

テストする機能やモジュールの入力値を与え、その結果が期待通りの出力になるかどうかを検証します。

テストの実行は自動化され、通常は特定のユニットテストフレームワークを使用して行われます。

ユニットテストは、プログラムのバグを早期に発見し、修正することができるため、ソフトウェア開発において非常に重要な役割を果たしています。

また、ユニットテストを十分に実施することで、プログラム全体の品質を向上させることができます。

結合テスト

結合テスト(同義語: 統合テスト)は、複数のソフトウェアコンポーネントを結合してシステム全体の動作を検証するテストのことです。

つまり、結合テストは、コンポーネントを結合して動作するシステムを作成し、そのシステムが正しく機能するかどうかをテストするプロセスです。

結合テストは、各コンポーネントを個別にテストするユニットテストがすでに完了していることが前提です。

それらのコンポーネントを結合してシステムを構築し、システム全体の動作を検証することによって、コンポーネント間のインタフェースや相互作用に問題がないかどうかを確認します。

結合テストには、機能的なテスト、性能テスト、信頼性テストなど、さまざまな種類があります。

プログラミングによる結合テストは、ソフトウェアの品質を確保するために非常に重要です。

システム全体の動作を検証することで、コンポーネントの間で生じる問題を早期に発見し、修正することができます。

これにより、システムの品質を向上させ、ユーザーが期待する性能や信頼性を確保することができます。

アプリケーションのコンポーネントがどのように連携するかをテストできます。

テストカバレッジで省略すべきもの

テストカバレッジは、ソフトウェアのテストを計画、設計、実行、報告するための重要な指標であり、開発者が品質管理プロセスを改善するための情報を提供します。

具体的には、コードカバレッジレポートを生成することによって、テストされた行、分岐、条件などのステートメントの割合を示します。

Reactのテストカバレッジを省略するかどうかは、プロジェクトの要件や目標によって異なります。

ただし、一般的には以下のようなものを省略することがあります。

1. テストしなくても安定して動作するコード

テストカバレッジを高めることは重要ですが、安定して動作するとわかっているコードは時間の無駄になる可能性があります。

そのため、テストを省略しても問題がないと思われる場合は、テストを行わないこともあります。

2. ライブラリやフレームワークに依存するコード

ライブラリやフレームワークのテストは、それらの開発者によって既に実施されている可能性があります。

そのため、依存するコードをテストすることはあまり意味がない場合があります。

3. ユーザーインターフェースに関連するコード

ユーザーインターフェースの変更が頻繁に発生する場合、テストを行っても意味がなくなる可能性があります。

そのため、ユーザーインターフェースに関連するコードのテストは省略されることがあります。

ただし、これらの場合でも、テストカバレッジを省略することが常に良いわけではありません。

プロジェクトの目標や要件、コードの品質に応じて、適切なテストカバレッジを設定する必要があります。

Jestとは?

Jestは、JavaScriptの一般的なテストフレームワークです。

ユニットテストで最も一般的に使用されており、近年はフロントエンドとバックエンドの両方のテストで人気が高まっています。

多くの大企業がReactテストにJestを採用しており、「Twitter」、「Instagram」、「Pinterest」、「Airbnb」などがその一例です。

Jestは、実際にはライブラリではなくフレームワークであり、テストランナー、アサーションライブラリ、CLIツール、および様々なモック手法のサポートを提供します。

これらすべてが、単なるライブラリではなく、フレームワークになります。

Jestの主な特徴は以下の通りです。

ゼロ構成: Jestは、JavaScriptのほとんどのプロジェクトで構成なしで動作することを目指しています。

プロジェクトの依存関係にJestをインストールするだけで、調整なしまたは最小限の調整で最初のテストを作成できます。

分離されたテスト:テストの分離は非常に重要なプロパティです。

異なるテストが互いの結果に影響を与えないように、Jestはテストを並列実行することができます。

これにより、実行時間を短縮し、開発者がより迅速にフィードバックを得ることができます。

スナップショット: Jestにはスナップショットテストがあります。

スナップショットは保存されたスナップショットと照合され、一致する機能がチェックされます。

これにより、オブジェクトのプロパティが存在し、正しい型を持っているかどうかを確認するために、アサーションで大量のテストを作成する必要がなくなります。

スナップショットテストは、ReactコンポーネントのUIのスナップショットをチェックするために使用することができます。

Reactコンポーネントのスナップショットを撮影すると、ReactがDOMに描画する前に、コンポーネントの出力を静的なマークアップにシリアル化します。

その後、後続のテストで、新しいスナップショットが以前のものと一致するかどうかを検証することができます。

これにより、UIの変更に対して迅速なテストを実行できます。

スナップショットテストは非常に便利ですが、注意が必要です。

スナップショットが変更された場合、テストが失敗するため、開発者はその変更を受け入れるか拒否するかを決定する必要があります。

これらのように、シンプルさを念頭に置いて設計されたフレームワークであり、分離されたテスト、スナップショットの比較、モック、テストカバレッジなどを構築するための強力で洗練されたAPIを提供します。

React Testing Library(RTL)とは?

React Testing Library(以下RTL)は、Reactコンポーネントをテストするために特別に構築された JavaScriptテストユーティリティです。

分離されたコンポーネントでユーザーの操作をシミュレートし、それらの出力をアサートして、UIが正しく動作していることを確認します。

また、RTLはJestに代わるものではありません。

それぞれが明確なタスクを実行し、コンポーネントをテストするには両方が必要です。

Reactのテストライブラリは当ブログの記事で詳しく解説していますので以下を先に参照ください。

RTLについて触れております。

dev-k.hatenablog.com

ユニットテストの構築

まずは、ReactアプリをJestとReact Testing Libraryを使用して、Reactコンポーネントユニットテスト(単体テスト)を行う方法を学習していきましょう。

Reactコンポーネントユニットテストは、初心者や既にテストに取り組んでいる経験豊富な開発者にとっては難しいかもしれません。

どのReactコンポーネントをテストする必要があるか、コンポーネント内のどのコードを正確にカバーする必要があるかを知る必要があります。

それでは、Reactアプリを作成します。

Create-React-Appを使用して、「react-jest-tutorial」という名前の新しいプロジェクトを作成します。

ターミナルで以下のコマンドを実行してください。

npx create-react-app react-jest-tutorial

cd react-jest-tutorial

次に、テストランナーとしてJestをインストールします。

Jestは、JavaScriptアプリケーションの自動テストを行うためのフレームワークです。

以下のコマンドを使用して、Jestをインストールしてください。

npm install jest @testing-library/react --save-dev

ここでは、Jestと共にReact Testing Libraryもインストールします。

RTLは、Reactコンポーネントをテストするためのユーティリティ関数を提供します。

Jestのスナップショットテストにも対応しています。

JestとReact Testing Library(RTL)を使用して、Reactアプリのユニットテストを行うための基本的なコード例を示します。

まずは、カウンターコンポーネントを作成します。

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  function incrementCount() {
    setCount(count + 1);
  }

  function decrementCount() {
    setCount(count - 1);
  }

  return (
    <div>
      <h1>Counter</h1>
      <p>Count: {count}</p>
      <button onClick={incrementCount}>Increment</button>
      <button onClick={decrementCount}>Decrement</button>
    </div>
  );
}

このコンポーネントでは、useStateフックを使用して、現在のカウンターの値を管理しています。

「incrementCount」と「decrementCount」関数は、カウンターの値を増加および減少させるために使用されます。

最後に、return文で、カウンターの値を表示するpタグと、インクリメントおよびデクリメントするための2つのボタンをレンダリングしています。

それでは、この単純なカウンターコンポーネントユニットテストします。

import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import Counter from './Counter';

test('increments the counter', () => {
  // テスト用の変数を作成し、カウンターコンポーネントをレンダリングする
  const { getByText } = render(<Counter />);
  
  // カウンターをインクリメントするボタンを取得し、クリックイベントを発生させる
  const incrementButton = getByText('Increment');
  fireEvent.click(incrementButton);
  
  // カウンターの値が1であることを確認する
  const counterValue = getByText('Count: 1');
  expect(counterValue).toBeInTheDocument();
});

test('decrements the counter', () => {
  // テスト用の変数を作成し、カウンターコンポーネントをレンダリングする
  const { getByText } = render(<Counter />);
  
  // カウンターをデクリメントするボタンを取得し、クリックイベントを発生させる
  const decrementButton = getByText('Decrement');
  fireEvent.click(decrementButton);
  
  // カウンターの値が-1であることを確認する
  const counterValue = getByText('Count: -1');
  expect(counterValue).toBeInTheDocument();
});

上記の例では、カウンターコンポーネントに対して2つのテストが行われます。

最初のテストは、カウンターをインクリメントするボタンをクリックした際に、カウンターの値が正しく増加するかどうかを確認します。

2番目のテストは、カウンターをデクリメントするボタンをクリックした際に、カウンターの値が正しく減少するかどうかを確認します。

どちらのテストも、renderメソッドを使用して、カウンターコンポーネントレンダリングし、「getByText」メソッドを使用して、ボタンを取得します。

その後、「fireEvent」メソッドを使用して、ボタンをクリックし、「expect」メソッドを使用して、カウンターの値が正しいかどうかを確認します。

最後に、「npx jest」コマンドを使用して、テストを実行します。

npx jest

Jestは、テストを実行し、どのテストが合格し、どのテストが失敗したかを報告します。

また、「npm test」コマンドでも問題ありません。

npm test

「npm test」コマンドは、「package.json 」ファイル内のtestスクリプトを実行するためのショートカットです。

通常、testスクリプトは「npx jest」を実行するように設定されています。

したがって、「npx jest」コマンドを実行しても同じ結果が得られます。

また、Jestの構成ファイルに手動でテストファイルのパスを追加する必要はありません。

Jestは、デフォルトで __tests__ディレクトリまたは *.test.js / *.spec.jsのファイルを自動的に検出し、テストファイルとして実行します。

そして、Jestは、 *.test.js / *.spec.jsファイルの場所にあるソースファイルと同じ場所にあるモジュールを自動的にインポートします。

したがって、テストファイルが正しいディレクトリに置かれ、正しいファイル名が付けられている限り、手動でテストファイルのパスを構成ファイルに追加する必要はありません。

手動でテストファイルのパスをJestの構成ファイルに追加するには、jest.config.jsファイルには、 testMatchオプションを使用して、テストファイルの正規表現パターンを指定することができます。

たとえば、 testMatchを以下のように設定することで、「tests 」ディレクトリにある.spec.js拡張子のファイルをテストファイルとして実行できます。

// jest.config.js

module.exports = {
  testMatch: [
    "**/tests/**/*.spec.js"
  ]
};

この設定は、 testsディレクトリとそのサブディレクトリにあるすべての .spec.js拡張子のファイルをテストファイルとして実行します。

次は、ユニットテストの応用編です、Buttonコンポーネントのテストファイルである「Button.js」を作成します。

// Button.js

// Buttonコンポーネントをインポート
import Button from './Button';

// React Testing LibraryからrenderとfireEventをインポート
import { render, fireEvent } from '@testing-library/react';

// 最初のテストケース: ボタンが正しいラベルを持つかどうかをテストする
test('renders a button with the correct label', () => {
  // Buttonコンポーネントを"Click me"というラベルでレンダリングする
  const { getByText } = render(<Button label="Click me" />);

  // ボタンのラベルが"Click me"であることを確認する
  const button = getByText('Click me');
  expect(button).toBeInTheDocument();
});

// 次のテストケース: ボタンがクリックされた時にonClickハンドラが呼び出されるかどうかをテストする
test('calls the onClick handler when clicked', () => {
  // onClickハンドラをモックする
  const onClick = jest.fn();

  // Buttonコンポーネントを"Click me"というラベルでレンダリングし、onClickハンドラを渡す
  const { getByText } = render(<Button label="Click me" onClick={onClick} />);

  // ボタンをクリックする
  const button = getByText('Click me');
  fireEvent.click(button);

  // onClickハンドラが1回だけ呼び出されたことを確認する
  expect(onClick).toHaveBeenCalledTimes(1);
});

このコマンドを実行すると、Jestが自動的に「./src」フォルダ内のすべてのテストファイルを探して、テストを実行します。

テストが完了すると、結果がコマンドラインに表示されます。

上記のコードでは、Buttonコンポーネントをテストするために、2つのテストを作成しました。

最初のテストは、Buttonコンポーネントが正しいラベルを持つボタンをレンダリングするかどうかを確認しています。

2番目のテストは、ボタンがクリックされたときにonClickハンドラが呼び出されるかどうかを確認しています。

テストには、「@testing-library/react」ライブラリの「render」関数と「fireEvent」関数が使用されています。

「render」関数は、Reactコンポーネントを仮想DOMにレンダリングするために使用されます。

「fireEvent」関数は、ユーザーのアクション(例えば、クリックなど)をシミュレートするために使用されます。

最初のテストでは、「render」関数を使用してButtonコンポーネントを仮想DOMにレンダリングし、「getByText」関数を使用してラベルが "Click me"のボタンを取得します。

その後、expect関数を使用して、buttonがドキュメント内に存在するかどうかを確認します。

「toBeInTheDocument」関数は、要素がドキュメント内に存在するかどうかを確認するために使用されます。

2番目のテストでは、onClickハンドラが正しく呼び出されるかどうかを確認するために、「fireEvent.click」関数を使用してボタンをクリックします。

Jestのmock関数である「jest.fn」を使用して、onClickハンドラが呼び出されたかどうかを確認します。

「toHaveBeenCalledTimes」関数を使用して、onClick関数が1回だけ呼び出されたことを確認します。

最後に、「npx jest」コマンドを使用して、テストを実行します。

npx jest

以上が、JestフレームワークとRTLライブラリを使用してReactアプリをユニットテストする方法の基本的な基礎から応用までの例です。

結合テストの構築

結合テストでは、複数のコンポーネントや機能を含むアプリケーションの振る舞いを確認するために、異なるユニットテストを結合して実行するテスト方法です。

では、前述のユニットテストを結合してみましょう。

// IntegratedButton.js

// Buttonコンポーネントをインポート
import Button from './Button';

// React Testing LibraryからrenderとfireEventをインポート
import { render, fireEvent } from '@testing-library/react';

// 結合テスト: ボタンが正しいラベルを持ち、クリックされた時にonClickハンドラが呼び出されるかどうかをテストする
test('renders a button with the correct label and calls the onClick handler when clicked', () => {
// onClickハンドラをモックする
const onClick = jest.fn();

// Buttonコンポーネントを"Click me"というラベルでレンダリングし、onClickハンドラを渡す
const { getByText } = render(<Button label="Click me" onClick={onClick} />);

// ボタンのラベルが"Click me"であることを確認する
const button = getByText('Click me');
expect(button).toBeInTheDocument();

// ボタンをクリックする
fireEvent.click(button);

// onClickハンドラが1回だけ呼び出されたことを確認する
expect(onClick).toHaveBeenCalledTimes(1);
});

この例では、Buttonコンポーネントを含むアプリケーションの振る舞いを確認するために、ユニットテストを結合しています。

結合テストでは、Buttonコンポーネントが正しくレンダリングされ、ラベルが正しいこと、クリックされた時にonClickハンドラが呼び出されることが確認されます。

これにより、ユニットテストよりも、より広範な機能テストが実行されます。

ユニットテストでは、通常1つのコンポーネントに焦点を当て、その振る舞いをテストします。

一方、結合テストでは、複数のコンポーネントが結合され、アプリケーション全体の振る舞いを確認します。

結合テストは、アプリケーションが期待通りの動作をすることを確認するために重要であり、バグを見つけるのに役立ちます。

JestとRTLのモック

Jestを使用すると、Reactコンポーネントをテストするための簡単な方法が提供されます。

モックの目的は、テストされたコードを例えば、API呼び出しなどの外部依存関係から分離することです。

つまり、テスト用に実際のオブジェクトを置き換え、テストの制御可能な状態を作ることです。

以下は、ReactとJestを使用してモックを実装する例です。

まず、Jestをインストールしている事を確認してください。

次に、以下のようなReactのCounterコンポーネントがあるとします。

import { useState } from 'react';

const Counter = () => {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
};

export default Counter;

このコンポーネントをテストするには、useStateフックをモック化する必要があります。

Jestの「jest.mock」関数を使用して、useStateフックをモック化します。

具体的には、useStateを呼び出す前に「jest.mock」を呼び出して、useStateをモック化することができます。

また、useStateの初期値を定義する必要もあります。

以下は、CounterコンポーネントをテストするためのJestとRTLのテストコードの例となります。

import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import Counter from './Counter';

jest.mock('react', () => ({
  ...jest.requireActual('react'),
  useState: jest.fn(),
}));

describe('Counter', () => {
  beforeEach(() => {
    // テストの前に useState の初期値を設定する
    useState.mockImplementation((init) => [init, jest.fn()]);
  });

  afterEach(() => {
    jest.resetAllMocks();
  });

  it('should increment count when the button is clicked', () => {
    const [count, setCount] = [0, jest.fn()]; // useStateフックをモック化する

    // useStateが初期化された時に返す値を設定する
    useState.mockImplementation(() => [count, setCount]);

    const { getByText } = render(<Counter />);
    const incrementButton = getByText('Increment');

    fireEvent.click(incrementButton);

    expect(setCount).toHaveBeenCalledWith(1);
  });
});

上記のコードでは、「jest.mock」を使用して、ReactのuseStateフックをモック化しています。

また、「beforeEach」ブロックを使用して、useStateフックの初期値を設定しています。

テストケースでは、useStateフックを呼び出す前にモック関数を定義し、useStateが初期化されたときにこのモック関数が返されるように設定しています。

そして、ボタンをクリックした際に、useStateのsetCountが1回呼び出されることを確認しています。

これにより、Counterコンポーネントのテストがモック化されたuseStateフックを使用して実行されます。

モックを使用することで、テストの制課をコントロールすることができ、テストの速度を向上させることができます。

また、依存関係を持つコードがテストされるときに、その依存関係のためにテストが失敗することがあるため、モックを使用して依存関係を取り除くことで、テストの信頼性を高めることができます。

しかし、モックを使用する際には、モックが原本の振る舞いを模倣しているかどうかに注意する必要があります。

モックが原本の振る舞いを正確に再現していない場合、本来は失敗すべきテストが成功したり、本来は成功すべきテストが失敗したりする可能性があります。

これが偽陽性偽陰性の結果を出すことになります。

例えば、モックが正しい値を返さずに常に同じ値を返すようになっていた場合、テストが成功してしまう可能性があります。

また、モックが本来の処理と異なる動作をする場合、テストが失敗してしまう可能性があります。

そのような、状況を避けるためには、モックが原本の振る舞いを正確に再現するように設定することが重要です。

以上が、React Testing Library(RTL)とJestを使用して、モックを作成する方法となります。

スナップショットのテスト

スナップショットテストは、UIが予期せず変更されないことを確認する場合に役立ちます。

典型的なスナップショットテストケースは、UIコンポーネントレンダリングし、スナップショットを取得して、保存されている参照スナップショットファイルと比較します。

2つのスナップショットが一致する場合、テストは成功します。

2つのスナップショットが一致しない場合は、予期しない変更が原因であるか、参照スナップショットを新しいバージョンのUIコンポーネントに更新する必要があることが原因である可能性があります。

前提条件として、スナップショットテストを作成するには、Reactコンポーネントを純粋なJavaScriptオブジェクトにレンダリングできるようにするために、「react-test-renderer」ライブラリをインストールする必要があります。

インストールコマンドは次のとおりです。

npm i react-test-renderer@17.0.1

次に、スナップショットテストを作成するために必要なファイルをインポートします。

以下のコードでは、 「axios」と「App」コンポーネントをインポートしています。

import axios from "axios";
import renderer from "react-test-renderer";
import App from "./App";

次に、App.jsファイルを編集してスナップショットテストを含めましょう。

// App.js

import  { useEffect, useState } from "react";
import axios from "axios";

function App() {

  // usersステートを定義し、初期値を空の配列に設定する
  const [users, setUsers] = useState([]);

  useEffect(() => {
    async function getUsers() { // 非同期処理を行う関数を定義する
      try {
        const response = await axios.get(`https://jsonplaceholder.typicode.com/users?name=${users}`);
        setUsers(response.data);
      } catch (error) {
        console.error(error);
      }
    }

    getUsers(); // 非同期処理を実行する関数を呼び出す
  }, [users]); // usersが変更されたときに再度フェッチする

  return (
    <div>
{/* ユーザーリストのタイトル */}
      <h1>User List</h1>
      <ul>
{/* usersをマップして、ユーザー名を表示する */}
        {users.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

上記のコードは、単純なUser Listを表示するReactコンポーネントです。

まず、useStateフックを使用して、usersの状態を定義し、初期値を空の配列に設定しています。

useEffectフックを使用して、コンポーネントがマウントされたときにAxiosを使用してユーザーデータを取得しています。

useEffectフックの第2引数にusersを渡しているため、usersが変更されたときだけ、再度Axiosを使用してユーザーデータを取得することができます。

取得したユーザーデータをsetUsersを使用して、usersの状態にセットします。

usersをマップして、ユーザー名をリスト形式で表示しています。

このコンポーネントは、スナップショットテストで使用可能です。

このAppコンポーネントの実際の動作をテストするためには、非同期処理が完了してからレンダリングを行う必要があります。

そのため、非同期処理が完了するまで待つためには、テストケースを非同期関数にして、 await キーワードを使って非同期処理を待つ必要があります。

以下の例では、 Appコンポーネントのスナップショットテストを作成しています。

import renderer from "react-test-renderer";
import axios from "axios";
import App from "./App"; // Appコンポーネントをインポート

// Appコンポーネントの非同期処理をモックする
jest.mock("axios");

// スナップショットテストを実行するテストスイート
describe("App component snapshot", () => {
  // スナップショットテストのテストケース
  test("renders correctly", async () => {
    // モックで返す偽のデータ
    const data = [
      { id: 1, name: "John" },
      { id: 2, name: "Jane" },
    ];
    // axios.getが呼ばれたときに、偽のデータを返すようにする
    axios.get.mockResolvedValue({ data });

    // Appコンポーネントをレンダリングし、スナップショットを取得
    const tree = renderer.create(<App />);

    // Appコンポーネントの非同期処理を待つ
    await act(async () => {
      await new Promise((resolve) => setTimeout(resolve, 0));
    });

    // スナップショットを検証する
    expect(tree.toJSON()).toMatchSnapshot();
  });
});

上記コードは、ReactアプリケーションのAppコンポーネントに対して、スナップショットテストを行うものです。

テスト前には、axios.get()メソッドをモックし、偽のデータを返すように設定しています。

テストでは、Appコンポーネントレンダリングし、レンダリング結果のスナップショットを取得します。

そのスナップショットを、以前に保存したスナップショットと比較して、一致しているかどうかを検証します。

このテストは、非同期でデータを取得する場合でも、レンダリング結果をスナップショットとして取得することができます。

ただし、非同期処理を行う場合は、適切なテスト方法を選択する必要があります。

そして、 「expect」関数を使用して、スナップショットとして保存された以前のコンポーネントの状態と比較します。

このスナップショットと比較することで、以前のコンポーネントの状態が変更されていないかどうかを確認することができます。

もし以前の状態と異なる場合、テストは失敗します。

以下は、スナップショットが異なる場合のテストが失敗する例です。

import renderer from "react-test-renderer";
import axios from "axios";
import App from "./App";

describe("App component snapshot", () => {
  beforeEach(() => {
    axios.get = jest.fn(() =>
      Promise.resolve({
        data: [
          { id: 1, name: "John" },
          { id: 2, name: "Jane" },
        ],
      })
    );
  });

  test("renders correctly", async () => {
    // Appコンポーネントをレンダリングし、スナップショットを取得
    const tree = renderer.create(<App />).toJSON();

    // スナップショットを検証する
    expect(tree).toMatchSnapshot(); // ここでスナップショットを取得し、以前に保存されたスナップショットと比較する

    // スナップショットが異なる場合は、テストが失敗します
  });
});

例えば、Appコンポーネントに表示するデータが変更された場合、以前のスナップショットと異なるスナップショットが取得されます。

そのため、テストが失敗します。

スナップショットの差異は、ターミナルに表示されます。

差異が正しい場合は、スナップショットを更新することでテストを通過させることができます。

また、テストがうまくいかない場合は、スナップショットに含まれる情報を確認することで、問題の箇所を特定することができます。

さらに、コンポーネントがクラッシュしないことを確認するために、もう1つのテストを追加することもできます。

このテストは、ReactDOM.renderメソッドを使用して、コンポーネントをDOMに描画し、「ReactDOM.unmountComponentAtNode」メソッドを使用してDOMからコンポーネントをアンマウントします。

例えば、以下のようなテストコードを書くことができます。

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

it('renders without crashing', () => {
  const div = document.createElement('div');
  ReactDOM.render(<App />, div);
  ReactDOM.unmountComponentAtNode(div);
});

it('renders correctly', () => {
  const tree = renderer
    .create(<App />)
    .toJSON();
  expect(tree).toMatchSnapshot();
});

最初のテストは、コンポーネントがクラッシュしないことを確認します。

このテストは、前述したようにReactDOM.renderメソッドを使用して、コンポーネントをDOMに描画し、「ReactDOM.unmountComponentAtNode」メソッドを使用してDOMからコンポーネントをアンマウントします。

2番目のテストは、スナップショットテストを行います。

初回の実行時にJestは、__snapshots__フォルダにスナップショットファイルを自動的に作成します。

このファイルには、レンダリングされたコンポーネントの出力が含まれます。

コンポーネントを変更すると、スナップショットファイルが自動的に更新されます。

更新されたファイルを確認し、変更内容が意図したものであることを確認する必要があります。

Jestが「watch mode」になっている場合、wを押して詳細を表示し、uを押してスナップショットを更新することができます。

これにより、テストをパスさせることができ、Jestにコンポーネントの意図的な変更を通知することができます。

最後に

Reactアプリケーションのテストは、高品質のアプリを作るための鍵であり、テストライブラリやJestを使用して、複数の種類のテストを実行でき、これまで以上に容易になりました。

これらは、テストを堅牢かつ効果的にするために役立ちます。

本日は以上となります。

最後まで読んで頂きありがとうございます。

この記事が役に立ったら、ブックマークし他の方にも共有して頂けると幸いです。

プライバシーポリシー

© 2023 Icons8 LLC. All rights reserved.

© [deve.K], 2023. React logo is a trademark of Facebook, Inc. JavaScript is a trademark of Oracle Corporation and/or its affiliates. jQuery and the jQuery logo are trademarks of the JS Foundation. TypeScript and the TypeScript logo are trademarks of the Microsoft Corporation. Next.js and the Next.js logo are trademarks of Vercel, Inc. Firebase and the Firebase logo are trademarks of Google LLC. All logos edited by [deve.K].