Reactの公式が推奨しているReact Testing Libraryを使ったコンポーネントのユニットテスト

date
Jan 2, 2023
repo_url
slug
testing-library
status
Published
summary
type
Post
thumbnail_url
tags
jest
outer_link

Reactにおけるユニットテスト

Reactのコンポーネントのテストを書くために使うツールはいくつか存在します。2022年時点ではReact Testing LibraryがReact公式で推奨されており、主流のツールとなっています。
 
React Testing LibraryはDOM Testing Libraryを使ってコンポーネントのテストを動かします。
DOMという名前がついている通り、コンポーネントを実際に描画して、その結果のDOMにアクセスをして正しく描画されているかテストします。React Testing Libraryにはそのために描画やイベント発行、DOMのプロパティへのアクセスなどの機能があります。React Testing Libraryを使うことで、見た目や振る舞いが正しく機能しているかテストできます。

テスト環境構築

Next.jsのプロジェクトにテスト環境を構築します。
①必要なパッケージのインストール
npm install --save-dev jest @testing-library/react @testing-library/jest-dom jest-environment-jsdom
 
②プロジェクトルートにjest.setup.jsを作成、以下を記述
import '@testing-library/jest-dom/extend-expect';
jest.setup.js
 
③プロジェクトルートにjest.config.jsを作成、以下を記述
const nextJest = require('next/jest');

const createJestConfig = nextJest({ dir: './' });

const customJestConfig = {
  testPathIgnorePatterns: ['<rootDir>/.next/', '<rootDir>/node_modules/'],
  setupFilesAfterEnv: ['<roodDir>/jest.setup.js'],
  testEnvironment: 'jsdom',
};

module.exports = createJestConfig(customJestConfig);
jest.config.js
 
④package.jsonにテストを実行するためのスクリプトを追加
{
  "scripts": {
    ...
		"test":"jest" 
  },
}
 

React Testing Libraryによるコンポーネントのユニットテスト

ここでは簡単な入力ボックスのコンポーネントをテスト対象とします。
 
次のコンポーネントはテキスト入力とボタンを描画し、テキストボックスに文字を入力でき、またボタンを押すことで入力した内容をリセットできます。
import { useState } from 'react';

type InputProps = JSX.IntrinsicElements['input'] & {
  label: string;
};

export const Input = (props: InputProps) => {
  const { label, ...rest } = props;

  const [text, setText] = useState('');

  const onInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setText(e.target.value);
  };

  const resetInputField = () => {
    setText('');
  };

  return (
    <div>
      <label htmlFor={props.id}>{label}</label>
      <input {...rest} type='text' value={text} onChange={onInputChange} />
      <button onClick={resetInputField}>Reset</button>
    </div>
  );
};
components/Input/index.tsx
 
同じディレクトリにindex.spec.tsxを作成します。
※テストファイルは.spec.tsxまたは.text.tsxで終了するファイル名にしなければなりません。
 
import { render, screen, RenderResult } from '@testing-library/react';
import { Input } from './index';

// describeで処理をまとめる
// describe関数を使うことでテストをまとめることができます。今回はInputコンポーネントのテストをするので、
// Inputという名前のグループを作成し、その中でテストを書いていきます。

describe('Input', () => {
// 中にテストケースを書いていく

  let renderResult: RenderResult;

  // テスト実行前の処理
  beforeEach(() => {
    // それぞれのテストケース前にコンポーネントを描画し、renderResultにセットする
    renderResult = render(<Input id='username' label='Username' />);
  });

  // テスト実行後の処理
  afterEach(() => {
    // テストケース実行後に描画していたコンポーネントを開放する
    renderResult.unmount();
  });

  // 初期描画時にinput要素が空であることをテスト
  it('初期描画時にinputは空です', () => {
    // labelがUsernameであるコンポーネントに対応するinputの要素を取得する
    const inputNode = screen.getByLabelText('Username') as HTMLInputElement;

    // expectに取得したinputのDOMを渡して、toHaveValueを実行し、
    // inputに指定した文字列(inputText)が入力されているかテスト
    // ここでは空文字を渡すことでinputに何も入力されていないことをチェックする
    expect(inputNode).toHaveValue('');
  });
});
components/Input/index.spec.tsx
 
npm run testでテストを実行できます。
テストが成功するとPASSと表示され、失敗するとエラーが表示されます。
 

input要素に文字を入力した場合に正しく表示されるかテスト

import {
  render,
  screen,
  RenderResult,
  fireEvent,
} from '@testing-library/react';
import { Input } from './index';

// describeで処理をまとめる
// describe関数を使うことでテストをまとめることができます。今回はInputコンポーネントのテストをするので、
// Inputという名前のグループを作成し、その中でテストを書いていきます。

// describe:いくつかの関連するテストを1つのブロックとしてまとめるためのメソッド
describe('Input', () => {
  // 中にテストケースを書いていく

  let renderResult: RenderResult;

  // テスト実行前の処理
  beforeEach(() => {
    // それぞれのテストケース前にコンポーネントを描画し、renderResultにセットする
    renderResult = render(<Input id='username' label='Username' />);
  });

  // テスト実行後の処理
  afterEach(() => {
    // テストケース実行後に描画していたコンポーネントを開放する
    renderResult.unmount();
  });

  // itの中では実際のテスト内容を記述する
  // 文字を入力したら、入力した内容が表示されるかをテスト
  it('入力されたテキストを表示', () => {
    const inputText = 'これはテストのテキストです';
    // 描画されているDOMから指定した名前(Username)のラベルに対応するinputを取得
    const inputNode = screen.getByLabelText('Username') as HTMLInputElement;

    // fireEnvetを使ってinput要素のonChangeイベントを発火する
    fireEvent.change(inputNode, { target: { value: inputText } });

    // expectに取得したinputのDOMを渡して、toHaveValueを実行し、inputに指定した文字列(inputText)が入力されているかテスト
    expect(inputNode).toHaveValue(inputText);
  });
});

ボタンがクリックされたときに入力している文字列がクリアされるかテスト

import {
  render,
  screen,
  RenderResult,
  fireEvent,
  getByRole,//追加
} from '@testing-library/react';
import { Input } from './index';

// describeで処理をまとめる
// describe関数を使うことでテストをまとめることができます。今回はInputコンポーネントのテストをするので、
// Inputという名前のグループを作成し、その中でテストを書いていきます。

// describe:いくつかの関連するテストを1つのブロックとしてまとめるためのメソッド
describe('Input', () => {
  // 中にテストケースを書いていく

  let renderResult: RenderResult;

  // テスト実行前の処理
  beforeEach(() => {
    // それぞれのテストケース前にコンポーネントを描画し、renderResultにセットする
    renderResult = render(<Input id='username' label='Username' />);
  });

  // テスト実行後の処理
  afterEach(() => {
    // テストケース実行後に描画していたコンポーネントを開放する
    renderResult.unmount();
  });

  // itの中では実際のテスト内容を記述する
  // ボタンがクリックされたら入力できストがクリアされるかチェック
  it('ボタンクリックで入力されたテキストをクリア', () => {
    const inputText = 'これはテストのテキストです';
    // 描画されているDOMから指定した名前(Username)のラベルに対応するinputを取得
    const inputNode = screen.getByLabelText('Username') as HTMLInputElement;

    // fireEnvetを使ってinput要素のonChangeイベントを発火する
    fireEvent.change(inputNode, { target: { value: inputText } });

    // ボタンを取得
    const buttonNode = screen.getByRole('button', {
      name: 'Reset',
    }) as HTMLButtonElement;
    // ボタンをクリックする
    fireEvent.click(buttonNode);

    // input要素の表示が空になったか確認
    expect(inputNode).toHaveValue('');
  });
});
 
 

© Hayato Kamiyama 2023 - 2024 - Build with Next.js & Notion