JavaScriptテストフレームワーク「Jest」を使って、Reactをテストしよう

ブログメインビジュアル こんにちは、フロントエンドエンジニアの峯です。
突然ですが、みなさんテストコード書いてますでしょうか?
「あ、確認漏れてた…」みたいなことってないでしょうか?


サービスを安全に運用していくために、テストを自動化する。テストフレームワークの活用は大変大事です。
今回は、そんなテストフレームワーク未経験者の為にJavaScriptテストフレームワークについて紹介します。

目次

事前準備

この記事では、テストツールの導入から簡単なサンプルテストを2例紹介します。
テスト対象とする環境は、Reactで紹介していきます。
利用可能なメソッドなどの違いはありますが、利用方法や記法について大きな違いはありません。


まずは、Reactの環境を整えます。
過去記事を参考に(ReactでTodoアプリの作成#reactアプリケーションの作成)Create React App を入れていただければ準備完了です!

Jestについて

今回利用する、「Jest」は、Facebook製のJavaScript フレームワークです。
もちろん、React依存のフレームワークではありません。ご自身の環境に合わせて利用いただけます。

テスト導入メリット

テストにはどのようなメリットがあるでしょうか。
フロントエンドエンジニアの身としては、画面触って確認する以上に確認方法ってない気もしますが、人間が行うとなると、担当者の思考の範囲でしかテストが行われず、どうしても漏れなどが発生してしまいがちです。


最低限の品質は、テストでカバー可能な状態が理想と言えます。
しかし、全てのテストを自動化することは実現性が低いです。
新しいエンジニアなど、仕様把握が十分でない状況の中で、既存機能が破壊されない状況(デグレを気づける環境)を作りやすくします。


テストルールは必要

闇雲にテスト実施するのは非効率です。
テストツールを導入する場合は、最低限テスト範囲を明確化することが大事です。


あくまで目的はデグレ防止だったり、人的ミスのサポートです。
(ここは確認しなくてもいいだろ←これデグレの原因)


手軽に始められるテスト

Create React Appには初めからテストパッケージが導入されてます。

src/App.test.js←テストファイル


すでにテストコマンドを叩けばテストを実施することは可能ですが、今回実施したいテストに合わせて、環境を整えていきます。
まず、react-test-rendererというパッケージをインストールします。

$ npm install --dev react-test-renderer


テスト導入時のファイル構成

Jestでは、ディレクトリ名、ファイル名をルールの通り名前を付けることで、自動的にテストファイルを識別して実行してくれます。
とは言っても、整理せずにファイルを配置するのはあまりよろしくないので、場所を決めましょう。

ディレクトリ名で指定する場合 → */__tests__/*
ファイル名で指定する場合 → */sample.test.js


【テスト1】-スナップテストをやってみよう

では、いよいよテストコードを書いていきます。
スナップテストはUIのテストです。
テスト実行時のDOM要素を比較することで、UIに予期せぬ変更が起きていないか確認することができます。


テストコードを書いてみよう

早速テスト対象場所にテストファイルを作成して、テストコードを書きます
今回は、src/配下にtests/というディレクトリを作成して、そこにテストファイルを作成します。

src/tests/sample.test.js

import React from 'react';
import renderer from 'react-test-renderer';
import App from '../App';

it('Page snapshot testing', () => {
  const component = renderer.create(<App />);
  expect(component).toMatchSnapshot();
});


完成です!(利用メソッドについては後述します)
テストコマンドを実行します!

$ npm test


テスト環境完了!

テスト対象のファイルがPASSしていれば、正常にテスト通ってます!

スナップテストの実行結果コンソール画面


テスト実行後、テストファイルのディレクトリに__snapshots__フォルダが作成されています。
このフォルダの中にスナップショットテスト実行時のrender要素がログとして残されています。
次回以降、テストを実行すると、このログとApp.jsが比較され、その結果が返されることになります。

テストメソッドについて

it(name, fn, timeout)or test(name, fn, timeout)
第1引数にテスト名を、第2引数にテストの確認項目を含む関数を設定します。
テスト実行時にコンソールに表示されるので、テスト対象がわかるように記載しましょう。


renderer.create()
React要素のインスタンスを作成します。
Reactのrender環境を表現します。


expect(value)
テストしたい値を読み出します。
返り値をテストする場合など、頻繁に利用するメソッドです。


.toMatchSnapshot(propertyMatchers?, hint?)
最新のスナップショットと比較します。
スナップショットのテストメソッドです。


【テスト2】-ユニットテストをやってみよう

ユニットテスト用のファイルを作成します。
テストするためのコンポーネント、Linkコンポーネント作ります。


渡ってきたpropsに応じて出力が変化するコンポーネントです。
(せっかくReactでテストするので、Reactテスト要素を出してみます。)
src/Link.js

import React from 'react';

class Link extends React.Component {
    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.jsも変更していきます。
リンク部分をLinkコンポーネントに差し替えます。

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

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

export default App;


テストコードを書いてみよう

テストの観点としては、2つあります。
Linkコンポーネントは、propsが渡された場合と、渡されない場合で表現が変化します。
この2つのパターンが正常の機能するかをテストしたいと思います。

src/tests/Link.test.jsx

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

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

    it('render', () => {
        // ケース別にテストします
        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');
    });
})

テストメソッドについて

describe(name, fn) Jestでは、テストのパッケージ化ができます。
describeメソッドに中では、テスト前後の共通処理に記述が可能です。
いくつかのテストを行いたい場合はこのメソッドでラップしてあげると見やすく、運用しやすいテストコードが書けるかと思います。


act() Reactのテスト関数です。render状態を表現します。


さいごに

テストコードを書いてみていかがでしたでしょうか?
テストスキルはエンジニアの1スキルとして、必須と言えるのではないでしょうか。


テストメソッドは実施したいテストに応じて、それぞれ用意されています。
ぜひ、今回紹介しなかったメソッドも含めて触ってみる機会になればと思います。


それから、テストコードは大変シンプルではありますが、テストコードもプログラムです。
テストコード内のバグには十分に注意するようにしましょう!
テストにバグがあっては大変です!


最後までご覧いただきありがとうございました。

この記事を書いた人 mine 2019年1月 中途入社のゴルフにはまっているフロントエンドエンジニア
COBOL開発経験がありますが平成生まれです。
TOP