create-react-appでreact-testing-libraryを使ってテストする

2023-03-18 hit count image

create-react-appで生成したReactプロジェクトでreact-testing-libraryを使ってテストをする方法について説明します。

create-react-appシリーズ

このブログポストはシリーズで作成しております。次はcreate-react-appのシリーズのリストです。

概要

以前のブログポストではJavaScriptテストフレームワークであるJestを使ってテストの基礎とJavaScriptのテストについて確認しました。今回のブログポストではcreate-react-appで生成したReactプロジェクトでreact-testing-libraryを使ってテストする方法について説明します。

このブログポストで紹介するソースコードは下記のリンクで確認できます。

react-testing-library

以前ブログで紹介したJestはJavaScriptテストフレームワークとして、JavaScriptの全般的なテストに使います。今回紹介するreact-testing-libraryはReactをテストするため最適化されたライブラリです。

create-react-appでReactプロジェクトを生成すると、Jestと同じようにreact-testing-libraryと一緒にインストールされます。したがって、特にインストールする必要なく、すぐにreact-testing-libraryを使うことができます。

今回のブログぷそつでは例題を使ってreact-testing-libraryを使う方法について説明します。

カウントアプリ開発

簡単な例題を使ってreact-testing-libraryを使う方法を確認するためReactプロジェクトを準備してみましょう。今回のブログポストでは下記のようなカウントアプリを作る要諦です。

Counter app

次のコマンドを使ってタイプスクリプトが適用されたReactプロジェクトを生成します。

npx create-react-app react-test --template=typescript

そして絶対パスでコンポーネントを追加するため、tsconfig.jsonファイルを開いて下記のように修正します。

{
  "compilerOptions": {
    ...
    "jsx": "react-jsx",
    "baseUrl": "src"
  },
  ...
}

最後にReactプロジェクトでstyled-componentsを使うため、下記のコマンドを使ってstyled-componentsをインストールします。

npm install --save styled-components
npm install --save-dev @types/styled-components jest-styled-components

Buttonコンポーネント

次はカウントアプリで値を加算したり減算するため使うボタンコンポーネントを作ってみましょう。ボタンコンポーネントを作るため./src/Components/Button/index.tsxファイルを生成して次のように修正します。

import React from "react";
import Styled from "styled-components";

interface ContainerProps {
  readonly backgroundColor?: string;
}

const Container = Styled.div<ContainerProps>`
  padding: 10px 15px;
  border-radius: 5px;
  background-color: ${(props) => props.backgroundColor};
  color: white;
  font-weight: bold;
  cursor: pointer;
`;

interface Props {
  readonly label: string;
  readonly backgroundColor?: string;
  readonly onClick?: () => void;
}
export const Button = ({ label, backgroundColor, onClick }: Props) => {
  return (
    <Container backgroundColor={backgroundColor} onClick={onClick}>
      {label}
    </Container>
  );
};

今回のブログではreact-testing-libraryを使ってテストする方法を説明するので、Reactのコンポーネントを作る方法についての説明は省略します。

Appコンポーネント

次は上で生成したButtonコンポーネントを使ってカウントアプリを生成してみましょう。./src/App.tsxファイルを開いて下記のように修正します。

import React, { useState } from "react";
import Styled from "styled-components";

import { Button } from "Components/Button";

const Container = Styled.div`
  display: flex;
  min-height: 100vh;
  align-items: center;
  justify-content: center;
  background-color: #F5F5F5;
`;

const Label = Styled.div`
  margin: 10px;
  width: 40px;
  text-align: center;
`;

function App() {
  const [count, setCount] = useState(0);
  return (
    <Container>
      <Button
        label="-"
        backgroundColor="#FF1744"
        onClick={() => setCount(count - 1)}
      />
      <Label>{count}</Label>
      <Button
        label="+"
        backgroundColor="#304FFE"
        onClick={() => setCount(count + 1)}
      />
    </Container>
  );
}

export default App;

このように作ったカウントアプリがうまく動いてるか確認してみましょう。次のコマンドを実行してカウントアプリを実行します。

npm start

問題なく実行されたら、ブラウザで次のような画面が確認されます。

create-react-app with typescript

テスト

次はこのように開発されたカウントアプリをJestとreact-testing-libraryを使ってテストをしてみましょう。まず、既存の./src/App.test.tsxファイルを削除します。そして下記のコマンドを実行してJestを実行します。

npm run test

Buttonコンポーネントのテスト

まず、Buttonコンポーネントをテストしてみましょう。Buttonコンポーネントをテストするため./src/Components/Button/index.test.tsxファイルを作って次のように修正します。

import React from 'react';
import { render, screen } from '@testing-library/react';
import 'jest-styled-components';

import { Button } from './index';

describe('<Button />', () => {
  it('renders component correctly', () => {
    const { container } = render(<Button label="button" />);

    const button = screen.getByText('button');
    expect(button).toBeInTheDocument();

    expect(container).toMatchSnapshot();
  });
});

一旦ReactをテストするのでReactライブラリを読み込む必要があります。

import React from 'react';

私たちはreact-testing-libraryのrenderscreenを使ってテストを進める予定です。

import { render, screen } from '@testing-library/react';

私たちはコンポーネントをデザインする時styled-componentsを使ってます。そのテストをするためのライブラリを追加します。

import 'jest-styled-components';

次は私たちがテストしたいButtonコンポーネントを読み込んで、Jestのdescribeitを使ってテストコードを作成する準備をします。

import { Button } from './index';

describe('<Button />', () => {
  it('renders component correctly', () => {
    ...
  });
});

まず、私たちが作ったButtonコンポーネントが画面へうまく表示されるかを確認するため、react-testing-libraryを使ってレンダリングします。

const { container } = render(<Button label="button" />);

このようにreact-testing-libraryのrenderを使ってテストしたいコンポーネントをレンダリングしたら、その結果でテストで使えるオブジェクトを返します。ここではスナップショットテストをするためcontainerを割り当てました。

Buttonコンポーネントを画面へ表示するためには必須Propsであるlabelを設定する必要があります。このように設定したlabelはボタンと一緒に画面へ表示されるので、私たちはこのlabelのテキストを画面から探して、画面に存在するかどうかを確認します。

const button = screen.getByText('button');
expect(button).toBeInTheDocument();

そのためreact-testing-libraryのscreen.getByTextを使って画面から、私たちがlabelを使って設定したテキストでコンポーネントを探して、当該コンポーネントが画面に表示されたかtoBeInTheDocumentで確認します。

最後にはtoMatchSnapshotを使ってスナップショットを保存します。

expect(container).toMatchSnapshot();

このように保存されたスナップショットはコードの変更から影響を受けたかどうかを知らせる役割をします。

このようにファイルを修正して保存したら、上で実行したnpm run testのコマンドによって自動でテストコードが実行されて、次のような結果が確認できます。

 PASS  src/Components/Button/index.test.tsx
  <Button />
    ✓ renders component correctly (27 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   1 passed, 1 total
Time:        2.46 s

次はButtonコンポーネントの他のPropsをテストしてみましょう。Buttonコンポーネントの必須ではないPropsであるbackgroundColorをテストするため次のような内容を追加します。

it('renders component with backgroundColor', () => {
  render(<Button label="button" backgroundColor="#FF1744" />);

  const button = screen.getByText('button');
  expect(button).toHaveStyleRule('background-color', '#FF1744');
});

まず、backgroundColorを設定したButtonコンポーネントを画面へ表示します。

render(<Button label="button" backgroundColor="#FF1744" />);

その後Buttonコンポーネントのlabelで画面へ表示されたButtonコンポーネントを探してjest-styled-componentsが提供してるtoHaveStyleRuleを使って当該スタイルを持ってるかどうかを確認します。

const button = screen.getByText('button');
expect(button).toHaveStyleRule('background-color', '#FF1744');

このようにファイルを修正して保存したら、上で実行したnpm run testのコマンドによって自動でテストコードが実行されて、次のような結果が確認できます。

 PASS  src/Components/Button/index.test.tsx
  <Button />
    ✓ renders component correctly (33 ms)
    ✓ renders component with backgroundColor (6 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   1 passed, 1 total
Time:        3.669 s
Ran all test suites.

最後に、Buttonコンポーネントのクリックイベントをテストしてみましょう。Buttonコンポーネントのクリックイベントをテストするため次のような内容を追加します。

import { render, screen, fireEvent } from '@testing-library/react';
...
it('clicks', () => {
  const onClick = jest.fn();
  render(<Button label="button" onClick={onClick} />);

  expect(onClick).toHaveBeenCalledTimes(0);
  const button = screen.getByText('button');
  fireEvent.click(button);
  expect(onClick).toHaveBeenCalledTimes(1);
});

ユーザのイベントをテストするためには、react-testing-libraryが提供するfireEventを使う必要があります。

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

そして実際イベントが発生した時、そのイベントを処理する関数へJestのMock Functionを適用します。

const onClick = jest.fn();
render(<Button label="button" onClick={onClick} />);

このように適用されたMock Functionはユーザのクリックイベントが発生した時、コールのカウントをチェックすることで実際クリックイベントが発生したかどうかを確認することができます。最初はクリックイベントが発生してないので当該関数のコールカウントは0になります。

expect(onClick).toHaveBeenCalledTimes(0);

次は実際react-testing-libraryのfireEventを使ってButtonコンポーネントをクリックしてみて、当該関数のコールカウントが増加したか確認します。

const button = screen.getByText('button');
fireEvent.click(button);
expect(onClick).toHaveBeenCalledTimes(1);

このようにファイルを修正して保存したら、上で実行したnpm run testのコマンドによって自動でテストコードが実行されて、次のような結果が確認できます。

 PASS  src/Components/Button/index.test.tsx
  <Button />
    ✓ renders component correctly (27 ms)
    ✓ renders component with backgroundColor (4 ms)
    ✓ clicks (7 ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   1 passed, 1 total
Time:        3.634 s
Ran all test suites.

これで私たちが作ったButtonコンポーネントについて全てのテストコードを作成してみました。

Appコンポーネントのテスト

次はAppコンポーネントをテストしてみましょう。AppコンポーネントはButtonコンポーネントとは違ってPropsがない代わり、Stateを使って動的なデータを管理しています。

Appコンポーネントをテストするため./src/App.test.tsxファイルを生成して次のように修正します。


import React from 'react';
import { render, screen } from '@testing-library/react';
import 'jest-styled-components';

import App from './App';

describe('<App />', () => {
  it('renders component correctly', () => {
    const { container } = render(<App />);

    const minusButton = screen.getByText('-');
    expect(minusButton).toBeInTheDocument();
    expect(minusButton).toHaveStyleRule('background-color', '#FF1744');
    const plusButton = screen.getByText('+');
    expect(plusButton).toBeInTheDocument();
    expect(plusButton).toHaveStyleRule('background-color', '#304FFE');
    const label = screen.getByText('0');
    expect(label).toBeInTheDocument();

    expect(container).toMatchSnapshot();
  });
});

Buttonコンポーネントで説明した内容は省略して進めます。まず、Buttonコンポーネントと同じようにAppコンポーネントが画面にうまく表示されたか確認するテストコードを作成してみました。AppコンポーネントはlabelbackgroundColorが違う二つのButtonコンポーネントとカウントを表示するコンポーネントを持っています。カウントの初期値は0なので画面から0が表示されたか確認することでカウントを表示するコンポーネントが画面に表示されたか確認しました。

const minusButton = screen.getByText('-');
expect(minusButton).toBeInTheDocument();
expect(minusButton).toHaveStyleRule('background-color', '#FF1744');

const plusButton = screen.getByText('+');
expect(plusButton).toBeInTheDocument();
expect(plusButton).toHaveStyleRule('background-color', '#304FFE');

const label = screen.getByText('0');
expect(label).toBeInTheDocument();

このようにファイルを修正して保存したら、上で実行したnpm run testのコマンドによって自動でテストコードが実行されて、次のような結果が確認できます。

 PASS  src/App.test.tsx
  <App />
    ✓ renders component correctly (34 ms)

 › 1 snapshot written.
Snapshot Summary
 › 1 snapshot written from 1 test suite.

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   1 written, 1 total
Time:        3.316 s

次は画面でマイナスボタンをクリックしてStateを使って画面へ表示されたカウントが変わるか確認してみましょう。マイナスボタンをクリックするために次のような内容を追加します。

import { render, screen, fireEvent } from '@testing-library/react';
...
it('clicks minus button', () => {
  render(<App />);

  const minusButton = screen.getByText('-');
  const label = screen.getByText('0');
  expect(label).toBeInTheDocument();

  fireEvent.click(minusButton);
  expect(label.textContent).toBe("-1");
  fireEvent.click(minusButton);
  expect(label.textContent).toBe("-2");
});

上でButtonコンポーネントをテストした方法と同じようにfireEventを使ってボタンをクリックして画面へ表示されあた値がうまく変更されたかテストしました。

このようにファイルを修正して保存したら、上で実行したnpm run testのコマンドによって自動でテストコードが実行されて、次のような結果が確認できます。

 PASS  src/App.test.tsx
  <App />
    ✓ renders component correctly (43 ms)
    ✓ clicks minus button (17 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   1 passed, 1 total
Time:        3.82 s

マイナスボタンと同じようにプラスボタンもテストしてみましょう。プラスボタンをテストするため次のような内容を追加します。

it('clicks plus button', () => {
  render(<App />);

  const plusButton = screen.getByText('+');
  const label = screen.getByText('0');
  expect(label).toBeInTheDocument();

  fireEvent.click(plusButton);
  expect(label.textContent).toBe("1");
  fireEvent.click(plusButton);
  expect(label.textContent).toBe("2");
});

プラスボタンのテストもマイナスボタンのテストと同じように画面へ表示されたプラスボタンをクリックして、画面に表示された値がうまく変更されたか確認しました。

このようにファイルを修正して保存したら、上で実行したnpm run testのコマンドによって自動でテストコードが実行されて、次のような結果が確認できます。

 PASS  src/App.test.tsx
  <App />
    ✓ renders component correctly (36 ms)
    ✓ clicks minus button (14 ms)
    ✓ clicks plus button (8 ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   1 passed, 1 total
Time:        3.705 s

これで私たちが開発したカウントアプリを全てテストしてみました。また、全てのテストがうまく動いてることを確認しました。

コードカバレッジ

そしたら、現在実行中のコマンドをキャンセルして、次のコマンドを実行してコードカバレッジを確認してみましょう。

npm run test -- --coverage

このようにコマンドを実行したら次のように全てのコードについてテストコードがうまく作成されたことが確認できます。

 PASS  src/Components/Button/index.test.tsx
 PASS  src/App.test.tsx
----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files |     100 |      100 |     100 |     100 |
 App.tsx  |     100 |      100 |     100 |     100 |
----------|---------|----------|---------|---------|-------------------

Test Suites: 2 passed, 2 total
Tests:       6 passed, 6 total
Snapshots:   2 passed, 2 total
Time:        2.752 s, estimated 3 s

完了

これでcreate-react-appで生成したReactプロジェクトをJestとcreate-react-appでテストする方法についてみてみました。ここで作成したテストコードが正解ではなく、色んな方法で同じテストコードを作成することができます。

また、コードカバレッジを使って私たちが作成したテストコードが全てのテストをカバーすることを確認しました。しかし、コードカバレッジをあくまでも確認用なので、100%で信頼しないように注意する必要があります。コードカバレッジが100%が出たことで現在作ったアプリへバグがなくて、私が作ったテストコードが全てのテストケースをテストしてると菅自害しないように注意しましょう。

私のブログが役に立ちましたか?下にコメントを残してください。それは私にとって大きな大きな力になります!

アプリ広報

今見てるブログを作成たDekuが開発したアプリを使ってみてください。
Dekuが開発したアプリはFlutterで開発されています。

興味がある方はアプリをダウンロードしてアプリを使ってくれると本当に助かります。

Posts