かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

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"
    ]
  }
}