オンプレ系インフラエンジニアがAzureを勉強する

いつか誰かの何かの役に立つと嬉しいな

【React+TypeScript】ミニマムなユニットテストコードを書いてみた

はじめに

React+TypeScriptでフロントエンドを触るようになりましたが、いまだにテストは全然自動化できてません…
テストの種類も色々あり、どれも気になるのですが、よくあるテストピラミッドを参考にまずはユニットテストを考えることにしました。
そもそもユニットテストのテストコードとはどんなものなのかを知りたかったので、ミニマムなテストコードを書いてみました。

使うもの

React
TypeScript
Reactのテストユーティリティ
テストユーティリティ – React

やったこと

新規Reactアプリケーションを作成する

下記コマンドで新規プロジェクトを作成します。

npx create-react-app [プロジェクト名] --typescript

テスト対象のコンポーネントを作る

テスト対象のコンポーネントを作ります。
今回はLink.tsxです。

import React, { Component } from 'react';

class Link extends Component<{ text?: string }, {}> {
    render() {
        return (
            <a
                className="App-link"
                href="https://reactjs.org"
                target="_blank"
                rel="noopener noreferrer"
            >
                {this.props.text || 'No contents'}
            </a>
        )
    }
}
export default Link

App.tsxで呼び出します。

import React from 'react';
import logo from './logo.svg';
import './App.css';
import Link from './components/Link';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <Link text='this link'/>
      </header>
    </div>
  );
}

export default App;

テストフォルダを作る

テストコードを格納するためにフォルダ名「tests」のフォルダを作ります。
f:id:mitsunooon:20210404170413j:plain

テストファイルを作る

testsフォルダにテストファイルを作ります。
今回はLink.text.tsx
テストファイルの書き方についてもまだ掘り下げ切れていないのですが、
確認項目ひとつにつき、actひとつという感じのようです。
今回は、propsが渡された場合と渡されない場合の2パターンでテストします。
propsが渡されない場合は、textが'No contents'であること
propsが渡された場合は、textが'this link'であることを確認しています。

import { render } from 'react-dom';
import { act } from 'react-dom/test-utils';
import Link from '../components/Link';

describe('Link component testing', () => {
    let container: Element | DocumentFragment | null = null;
    container = document.createElement("div");
    document.body.appendChild(container);

    it('Linkコンポーネントテスト', () => {
        // ケース別にテストします
        act(() => {
            render(<Link />, container);
        });
        // propsが渡されない場合
        expect(container!.textContent).toBe('No contents');

        act(() => {
            render(<Link text='this link'/>, container);
        });
        // propsが渡された場合
        expect(container!.textContent).toBe('this link');
    });
})

App.test.tsxの編集

create-react-appすると、デフォルトでApp.test.tsxというテストファイルが作られますが、
そのまま使用するとエラーが出るので、下記のように書き換えるorコメントアウトします。

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

test('renders learn react link', () => {
  render(<App />);
  // const linkElement = screen.getByText(/learn react/i);
  // expect(linkElement).toBeInTheDocument();
  const linkElement = screen.queryByText(/learn react/i);
  expect(linkElement).toBeNull();
});

テストの実行

ターミナルで下記コマンドを実行して、テスト開始します。

npm test


問題がなければ2 passedの結果が出ます。
f:id:mitsunooon:20210404170329j:plain

意図的に失敗させたい場合は、Link.text.tsx内の'this link'を別の文字列にしたりします。
そうするとこんな感じのエラーが出ます。
f:id:mitsunooon:20210404170344j:plain

ソースコード全体

今回作ったもののソースコードGitHubにあげているの、そちらも参照ください。
github.com

感想

今回は2つしかテストがないので、結果が見やすいですがテストが増えたら見づらくなるのでは?と思ってます。
たぶんVScode拡張機能を使ったりすればもっと見やすいテスト結果の取得ができるんだろうなという気がします。

今回のやり方では必要なコンポーネントごとにテストコードを書くのは結構な労力になることがわかりました。
この考え方をベースにしてユニットテストコードを書くのは果たして正解なのか…
ユニットテストコードを書くことはどんなやり方でもこれくらい大変なのか、
またはもっと簡単なやり方があるのか、慣れなのか…ううーむ

少なくともテストコードの作成には労力が結構必要ということが身をもってわかったので、必要なところにピンポイントで用意することの大切さはわかった気がします。
(むやみやたらにテストコードを用意すればいいものではない、コスト的にも難しい…等

多少大変でも品質向上のために適材適所なテストコードは必要かと思っているので
とりあえず導入できるくらいのテストコードは模索したいと思います。