かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

React + TypeScriptでjestを使ったテストをする

Reactを使ったアプリのテストにはjestっていうのが使われるっぽいですね?jest知らない子だ。

facebook.github.io

Mock by Defaultとか激しいですね。

軽く使ってみる

テスト対象のスクリプトのあるフォルダに__tests__というフォルダを作ってその下に「***-test.js」という感じでファイルを作るっぽいですね。

インストール

React以外に、以下のものをインストールしておきます。最後のやつはReactをテストするときの便利メソッドとかが定義されてるものです。

npm install jest-cli -g
npm install jest
tsd install jest
tsd install react-addons-test-utils

Test Hello world

ということで以下のような足し算するクラスを作ってみました。scripts\Calc.tsという名前で

export default class Calc {
    add(x: number, y: number) {
        return x + y;
    }
}

そして、scripts\__tests__\Calc-test.tsという名前で以下のようなファイルを作ります。

/// <reference path="../../typings/jest/jest.d.ts" />
jest.dontMock('../Calc');
import Calc from '../Calc';

describe('calc', () => {
    it('add', () => {
        expect(12).toEqual(new Calc().add(10, 2));
    });
});

まず、jest.dontMockでモックにしないモジュールを指定します。その後、テスト対象のモジュールを読み込みます。

describeでテストクラスを定義するようなイメージで、itでテストメソッドを定義するようなイメージです。expectでテストの期待値を指定してto***メソッドでアサーションするという感じです。

Reactのテストは

以下のようなコンポーネントをテストすることを考えてみます。

scripts\Header.tsxという名前でファイルを作ります。

import * as React from 'react';

interface HeaderProps extends React.Props<{}> {
    title: string;
    onClick: () => void;
}

export default class Header extends React.Component<HeaderProps, {}> {
    render() {
        return (
            <div>
                <h1>{this.props.title}</h1>
                <hr />
                <button onClick={() => this.props.onClick()}>OK</button>
            </div>
        );
    }
}

そして、テスト用のファイルとしてscripts\__tests__\Header-test.tsxを作ります。tsxなところ注意。

/// <reference path="../../typings/jest/jest.d.ts" />
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import TestUtils = require('react-addons-test-utils');

jest.dontMock('../Header');
import Header from '../Header';

describe('header test', () => {
    it('title', () => {
        var mockHandler = jest.genMockFunction();
        var h = TestUtils.renderIntoDocument(
            <Header title='Hello' onClick={mockHandler}/>
        );
        var h1 = TestUtils.findRenderedDOMComponentWithTag(h, 'h1');
        expect(h1.textContent).toEqual('Hello');
        
        var button = TestUtils.findRenderedDOMComponentWithTag(h, 'button');
        TestUtils.Simulate.click(button);
        expect(mockHandler).toBeCalled();
    });
});

まず、react系のものを読み込んでおきます。react-addons-test-utilsはテスト用です。そして、先ほど作ったHeaderをモックにしないように指定してからimportします。

itの中ではjest.getMockFunction()でモックのファンクションを作ってから、TestUtils.renderIntoDocumentメソッドでReactのコンポーネントを作ります。このとき、onClickに先ほど作ったモックのファンクションを渡します。

TestUtilsのfindRenderedDOMComponentWithTagでコンポーネントからタグを取り出せます。textContentを使って中身のアサーションをしたり、TestUtils.Simulate.clickでボタンのクリックをシミュレートしてtoBeCalledでモックのファンクションが呼び出されたことなどをアサーションできます。

package.jsonの指定

Reactのファイルがモックにされないようにpackage.jsonに以下の行を追加しておきます。

"jest": {
  "unmockedModulePathPatterns": [
    "<rootDir>/node_modules/react",
    "<rootDir>/node_modules/fbjs"
  ]
}

実行の準備

package.json scriptsのところにtestを追加します。デフォルトで何も指定しないとechoが仕込まれてるはずなので、以下のように書き換えます。

"scripts": {
  "test": "jest"
},

実行

PowerShellあたりでnpm testと打ち込むと以下のようにテストが実行されます。

> jest

Using Jest CLI v0.8.2, jasmine1
 PASS  scripts\__tests__\Calc-test.js (0.033s)
 PASS  scripts\__tests__\Header-test.js (0.539s)
2 tests passed (2 total in 2 test suites, run time 1.519s)

所感

Visual Studioのテストランナーとのインテグレーションをしてくれるものはないっぽいので、適当に自分で手打ちするしかなさそうです。こういうのが増えてくると、タスクランナーがほしくなってくるんでしょうね。まだ、大丈夫。

package.json全体

一応jestの定義とかあるので、全体を載せておきます。

{
  "name": "helloworld",
  "version": "1.0.0",
  "description": "",
  "main": "scripts/app.js",
  "scripts": {
    "test": "jest"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "jest": "^0.1.40",
    "react": "^0.14.5",
    "react-addons-test-utils": "^0.14.5",
    "react-dom": "^0.14.5"
  },
  "jest": {
    "unmockedModulePathPatterns": [
      "<rootDir>/node_modules/react",
      "<rootDir>/node_modules/fbjs"
    ]
  }
}