💡

React Testing Libraryでテストをやってみる

2023/04/03に公開

React Testing Libraryを使ってみた時のメモです。
バックエンドのテストを勉強していた際、単体テストを行いやすいようにレイヤードアーキテクチャを意識してリファクタリングしたことで大きな学びになった経験から、フロントエンド(React)のテストについても勉強することで新しく何か学べるのではないかと思い、調査してみました。

React Testing Libraryを勉強した理由は以下2点です。

  • React公式ドキュメントで推奨されていること
  • create-react-appを使った場合にテスト環境がデフォルトで作られており、
    テスト環境の構築に悩まずにReactのテストが勉強できること

https://ja.reactjs.org/docs/testing.html

また、JestとReact Testing Libraryは以下の関係性のようです。

  • JavaScriptのTesting Framework/Test Runnerであり、テストのファイルを自動で探し出し、テストを実行、テストを実行した結果期待通りの正しい値を持っているか関数(matchers)を利用してチェックを行い、テストが成功か失敗かの判断を行うことができる
  • React Tesing Libraryはテストを実行したいコンポーネントの描写やクリックイベントの実行、描写した内容からの要素の取得等に利用される

https://reffect.co.jp/react/react-test#Testing_LibraryJest

環境構築

バージョン

  • Node.js 18.14.2
  • React 18.2.0
  • React Testing Library 13.4.0
  • TypeScript 4.9.5

インストール

ターミナル
// ReactとTypeScriptのプロジェクトを作成
$ npx create-react-app sample-app --template typescript

// 作成したプロジェクトに移動
$ cd sample-app

// プロジェクトの中のApp.test.tsxでテストが動くか確認する
$ npm run test
Watch Usage
 › Press a to run all tests.
 › Press f to run only failed tests.
 › Press q to quit watch mode.
 › Press p to filter by a filename regex pattern.
 › Press t to filter by a test name regex pattern.
 › Press Enter to trigger a test run.

// pを入力する
// pattern > にファイル名App.test.tsxを指定してEnterで実行
Pattern Mode Usage
 › Press Esc to exit pattern mode.
 › Press Enter to filter by a filenames regex pattern.

 pattern › App.test.tsx

 Pattern matches 1 file
 › src/App.test.tsx
 
// 結果が出力される(passedが出力されればOK)
 PASS  src/App.test.tsx
  ✓ renders learn react link (17 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.592 s, estimated 1 s
Ran all test suites matching /App\.test\.tsx/i.

VSCodeの拡張機能JestRunnerで実行したい場合

プロジェクト直下(今回の場合はsample-appフォルダ)に.vscodeフォルダを作成後、settiings.jsonを新規に作成し、以下の内容を書き込む。

フォルダ階層
sample-app/
└─ .vscode/
       └─ settiings.json
settiings.json
{"jestrunner.jestCommand": "npm run test --"}

簡単なテストをやってみる

テスト対象のコンポーネントによってテキストが表示されているか確認する

src/App.test.tsx
import React from 'react';
import { render, screen } from '@testing-library/react';

// テスト対象のReactコンポーネント
export const PageHeader: React.FC = () => {
  return (
      <p>ServiceName</p>
  );
};

test("ページ中に「ServiceName」が表示される。", () => {
  render(<PageHeader />);
  expect(screen.getByText("ServiceName")).toBeInTheDocument()
});

axiosライブラリをモック化する

package.json内の"script"の"test"を書き換える必要がある。

package.json
"scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --transformIgnorePatterns \"node_modules/(?!axios)/\"",
    "eject": "react-scripts eject"
},
src/App.test.tsx
import React from 'react';
import { render, screen } from '@testing-library/react';
import axios from "axios";

// axiosのモック化
jest.mock('axios');
const axiosMock = axios as jest.Mocked<typeof axios>;

// モックしたいGETリクエスト
const getReq = async() => {
  try {
    const res = await axios.get("https://~");
    return res;
  }
  catch (error) {
    console.log(error);
  }
}

// テスト対象のReactコンポーネント
export const PageHeader: React.FC = () => {
  return (
      <p onClick={
        async()=>{
          const temp = await getReq();
        }
      }>ServiceName</p>
  );
};

test("ページ中に「ServiceName」が表示される。", () => {
  axiosMock.get.mockResolvedValueOnce("ServiceName");
  render(<PageHeader />);
  expect(screen.getByText("ServiceName")).toBeInTheDocument()
});

コンポーネントを指定してテストする

指定したいコンポーネントにdata-testid属性を記述する。今回はイベントの発火のためにfireEventを使用(userEventが推奨されている?要調査)。

src/App.test.tsx
import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
import styled from 'styled-components';

const ServiceNameContainer = styled.div`
  background-color: red;
  cursor: pointer;
  &:active {
    background-color: blue;
  }
`

// テスト対象のReactコンポーネント
// data-testid -> dom要素を取得するために属性を追加
export const PageHeader: React.FC = () => {
  const [name, setName] = React.useState<string>("");
  return (
    <ServiceNameContainer
      data-testid="testServiceNameContainer"
      onClick={() => setName("ServiceName")}>
      <p>{name}</p>
    </ServiceNameContainer>
  );
};

test("ページ中に「ServiceName」が表示される。", () => {
  render(<PageHeader />);
  fireEvent.click(screen.getByTestId("testServiceNameContainer"));
  expect(screen.getByText("ServiceName")).toBeInTheDocument()
});

テストを書く際のメモ

  • 実装する前にdescrice、itで言葉でテストケースを書き出した方が良い
    →8割ほど書き出したらもう実装できているようなものという意見も聞いた

  • テストケースは細かく書く
    →テストを書く中で認識違いを経験した(Altが表示されればOK?図まで表示されればOK?)
    →何を入力として何を出力すればテスト成功とするかを明確に書くべき

  • テストを実装する際には、公式と個人ブログで調査する

https://testing-library.com/docs/react-testing-library/cheatsheet
https://jestjs.io/ja/docs/tutorial-react

以上です。

NCDCエンジニアブログ

Discussion