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.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);
④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>
);
};
同じディレクトリに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('');
});
});
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('');
});
});