Create-React-Appで作ったReact アプリでJest & Enzymeを使ってユニット・テストを試してみる

create-react-appでプロジェクトを作成してテストを実行する

create-react-appでtestingというプロジェクトを作成します。

create-react-app testing
create-react-app でアプリを作成するとtestingというフォルダの中に必要なフォルダやファイルが自動で作成されます。
create-react-appはReact, Webpack, Babel, Jestなどのライブラリを自動的にインストールしてくれます。
その中で、Jestというのが自動テスト用のライブラリです。
create-react-appはテストのためのBabelの設定等をデフォルトで行ってくれているのですぐにテストを行うことができます。
基本的にはsrcフォルダ内のファイルを編集してReactアプリを組み立てていきます。

最初のテスト

さっそく簡単なテストを実行してみます。

App.jsを以下のように編集します。

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {
  render() {
    return <div className="App">こんにちわ</div>;
  }
}

export default App;

「こんにちわ」とブラウザ画面上に表示するだけのアプリです。
このアプリが期待通りの動作をしているかテストします。

create-react-app でtestingプロジェクトを作成した時点でsrc内にApp.test.jsというファイルが作成されています。これにexpect(div.innerHTML).toContain('こんにちわ');を追記します。
App.test.jsは以下になります。

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

it('renders without crashing', () => {
  const div = document.createElement('div');
  ReactDOM.render(<App />, div);

  expect(div.innerHTML).toContain('こんにちわ');
  ReactDOM.unmountComponentAtNode(div);
});

it('renders without crashing', () => { ....
の it は英語のit(それ)です。
it renders without crashing (直訳すると「それはクラッシュすることなく描画しました」)
です。it(それ) はテストを意味してますね。

expect(div.innerHTML).toContain('こんにちわ');<App />内のinnerHTMLに「こんにちわ」が含まれているかどうかをテストします。”toContain”は含まれているかどうかをチェックする関数です。

npm run test
とするとtestが実行されます。

と表示されれば成功です。
Tests : 1passed, 1 totalとなっています。無事にテストがパスしています。

テストを終了する時はCtrl + Cを押します。

テストが実行される流れ

Enzymeライブラリをインストール&設定する

もっと複雑なテストを簡単に行うためにEnzymeというテストユーティリティライブラリを使用します。
EnzymeはAirbnb製のオープンソースライブラリです。
enzymeのドキュメントはこちら → https://airbnb.io/enzyme/
Enzymeをreactで使用する場合2つのライブラリをインストールする必要があります。reactのバージョンが16の場合、enzymeenzyme-adapter-react-16をインストールします(reactのバージョンによって16の部分を15または17などに変更する必要があります)。

npm install enzyme enzyme-adapter-react-16

setupTests.jsというファイルをsrcフォルダ内に作成する

Jestはsrcフォルダ内のsetupTests.jsというファイルを探して設定を読み込みます。setupTests.jsという名前でファイルを作る必要があります。

setupTests.js

import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

Enzyme.configure({ adapter: new Adapter() });

Enzyme API

Static render

指定したコンポーネントを描画してそのHTMLを返します。

Shallow render

指定されたコンポーネントのみ描画して子コンポーネントは描画しない。
つまり、指定したコンポーネントの動作のみ確認できる。

Full DOM render

すべての描画を行います。

beforeEach → it → afterEach

Full DOM renderを行う時にはmountとunmountを使用します。

コンポーネントのマウントやアンマウントなどの操作は多くのitで共通して行うので、Jestの関数であるbeforeEachやafterEachを使って、共通する作業をitの外に出します。

let wrapped;

beforeEach(() => {
  wrapped = mount(<CommentBox />);
});

afterEach(() => {
  wrapped.unmount();
});

elementがあるかどうかテストする

mountとunmountをbeforeEachとafterEach内で行ったので、itはより簡潔に書くことができます。

it('has a text area and a button', () => {
  expect(wrapped.find('textarea').length).toEqual(1);
  expect(wrapped.find('button').length).toEqual(1);
});

文字入力ができるかテストする

  1. textarea elementがあるかどうかチェック
  2. changeイベントをシミュレート
  3. 仮のイベントを渡してみる
  4. コンポーネントをアップデートする
  5. textareaの値が変更されたかを確認する

changeイベントをenzymeのsimulateでシミュレート

enzyme の simulateメソッドを使う。
仮の値(mock)をsimulateの第2引数として渡すことができます。
React ではonChangeですが、simulate('change')としてシミュレートします。
wrapped.update()でコンポーネントを確実にアップデートする。

その後、expectでexpect(wrapped.find('textarea').prop('value')).toEqual('new comment');
valueの値を読み込んで(.prop)渡した文字列'new comment'と同じであるか(.toEqual)比較しています。

.propはelementのプロパティ値を取得することができます。

import React from 'react';
import { mount } from 'enzyme';
import CommentBox from 'components/CommentBox';

let wrapped;

beforeEach(() => {
  wrapped = mount(<CommentBox />);
});

afterEach(() => {
  wrapped.unmount();
});

it('has a text area and a button', () => {
  expect(wrapped.find('textarea').length).toEqual(1);
  expect(wrapped.find('button').length).toEqual(1);
});

it('has a text area that users can type in', () => {
  wrapped
    .find('textarea')
    .simulate('change', { target: { value: 'new comment' } });
  wrapped.update();
  expect(wrapped.find('textarea').prop('value')).toEqual('new comment');
});

it('when form is submitted, text area gets emptied', () => {
  wrapped.find('textarea').simulate('change', {
    target: { value: 'new comment' }
  });
  wrapped.update();
  wrapped.find('form').simulate('submit');
  wrapped.update();
  expect(wrapped.find('textarea').prop('value')).toEqual('');
});

重複する動作を外に出す

wrapped
      .find('textarea')
      .simulate('change', { target: { value: 'new comment' } });
    wrapped.update();

は重複して2つのit(...)でてくるので、

describe('テキストエリア', () => {
  beforeEach(() => {
    wrapped
      .find('textarea')
      .simulate('change', { target: { value: 'new comment' } });
    wrapped.update();
  });
  it('has a text area that users can type in', () => {
    expect(wrapped.find('textarea').prop('value')).toEqual('new comment');
  });

  it('when form is submitted, text area gets emptied', () => {
    wrapped.find('form').simulate('submit');
    wrapped.update();
    expect(wrapped.find('textarea').prop('value')).toEqual('');
  });
});

のように、共通する操作を持つitdescribeで切り出してきてその中でbeforeEachを適用します。

Reduxを使ったステート管理をテストするには

 

 

コメント