tanaka's Programming Memo

プログラミングについてのメモ。

Jestの公式サイトのTutorial-React

am1tanaka.hatenablog.com

公式サイトのチュートリアルの意訳です。

JestによるJavaScriptの単体テスト - tanaka's Programming Memoで作成したテスト用フォルダーで試すと手軽です。

2016/2/28時点では、公式サイトの情報が古くなっていてそのままでは動作しませんでした。公式サイトからリンクが貼られている jest/examples/react at master · facebook/jest · GitHub が本当の最新版なので、コードや設定はそちらに合わせて修正してあります。

公式サイトより

2つのラベルを交換する簡単なチェックボックスの実装をJestでテストしてみましょう。

(追記:ES2015(ES6)の書式で書かれたJavaScriptコードです。ES2015の書式にしないとReactのstateの初期化時にブラウザーで警告が表示されます。ソースコードを入れるフォルダーにCheckboxWithLabel.jsを作成して、コードを入力して保存してください。)

// CheckboxWithLabel.js
import React from 'react';

export default class CheckboxWithLabel extends React.Component {

  constructor(props) {
    super(props);
    this.state = {isChecked: false};

    // bind manually because React class components don't auto-bind
    // http://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html#autobinding
    this.onChange = this.onChange.bind(this);
  }

  onChange() {
    this.setState({isChecked: !this.state.isChecked});
  }

  render() {
    return (
      <label>
        <input
          type="checkbox"
          checked={this.state.isChecked}
          onChange={this.onChange}
        />
        {this.state.isChecked ? this.props.labelOn : this.props.labelOff}
      </label>
    );
  }
}

テストコードは非常にシンプルです。ReactのTestUnitsを使って、Reactコンポーネントを制御します。
(以下のコードを、__tests__フォルダー内にCheckboxWithLabel-test.jsというファイル名で作成します。)

// __tests__/CheckboxWithLabel-test.js
/* eslint-disable no-unused-vars */
jest.unmock('../CheckboxWithLabel');

import React from 'react';
import ReactDOM from 'react-dom';
import TestUtils from 'react-addons-test-utils';
import CheckboxWithLabel from '../CheckboxWithLabel';

describe('CheckboxWithLabel', () => {

  it('changes the text after click', () => {
    // Render a checkbox with label in the document
    const checkbox = TestUtils.renderIntoDocument(
      <CheckboxWithLabel labelOn="On" labelOff="Off" />
    );

    const checkboxNode = ReactDOM.findDOMNode(checkbox);

    // Verify that it's Off by default
    expect(checkboxNode.textContent).toEqual('Off');

    // Simulate a click and verify that it is now On
    TestUtils.Simulate.change(
      TestUtils.findRenderedDOMComponentWithTag(checkbox, 'input')
    );
    expect(checkboxNode.textContent).toEqual('On');
  });
});

Setup

ReactのJSXコードを書いたので、テストに必要なセットアップを一度実施します。Jestのためにbabel-jestパッケージを利用します。また、Reactも必要です。package.jsonを開いて、各設定で見当たらない宣言を追加してください。

// package.json
  "dependencies": {
    "react": "~0.14.0",
    "react-dom": "~0.14.0"
  },
  "devDependencies": {
    "babel-jest": "*",
    "babel-preset-es2015": "*",
    "babel-preset-react": "*",
    "babel-preset-jest": "*",
    "jest-cli": "*",
    "react-addons-test-utils": "~0.14.0"
  },
  "scripts": {
    "test": "jest"
  },
  "jest": {
    "scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
    "unmockedModulePathPatterns": [
      "<rootDir>/node_modules/react",
      "<rootDir>/node_modules/react-dom",
      "<rootDir>/node_modules/react-addons-test-utils",
      "<rootDir>/node_modules/fbjs"
    ]
  }

以上を追加して上書き保存したら、ターミナルで npm install を実行して追加したものをインストールします。

jestを実行する際に、es2015形式を有効にして、reactをビルドする必要があります。ソースフォルダーに .babelrc というファイルを新規に作成して、以下のコードを追加して上書き保存します。

{
    "presets": ["es2015", "react"],
    "env": {
        "test": {
            "presets": ["jest"]
        }
    }
}

ReactはTestUtilsを使って、モック化をしないでテストできるように設計されています。そのため、Reactがモック化されるのを防ぐために unmockedModulePathPatterns(モック化しないモジュールのパスパターン) を利用します。(package.jsonに追加した"jest"の設定がそれ)。

以上で準備完了です。 npm test でテストしてください。

テストが失敗する場合

この原稿を書いている2016/2/29現在、npmでインストールされるjest-cliでは、Reactのテストが失敗(FAIL)します。その場合は、以下の手順でjest-cliのnodeモジュールをGitHubのものに差し替えてください。

  • ターミナルのソースフォルダーから、以下を実行してjest-cliを削除
rm -rf node_modules/jest-cli
git clone --depth 1 https://github.com/facebook/jest.git node_modules/jest-cli
  • jest-cli内のgit情報は不要なので削除
rm -rf node_modules/jest-cli/.git
  • npm install を実行して、必要なパッケージをインストール

以上をやったら、 npm test でテストを実行します。今度はうまくいくと思います。

この例のコードは examples/react/ にあります。

補足情報

以下、補足情報です。試していないので、もしかしたら動かないかもしれません。

bebel-jestを利用してテスト用の環境を使う

デフォルトの振る舞いでは、babel-jestはBabelのデフォルトステージであるstage2を利用します。もし、他のステージを利用したい場合は、package.jsonの"scripts"の設定を以下のように設定します。

  "scripts": {
    "test": "BABEL_JEST_STAGE=0 jest"
  }

独自のプリプロセッサの利用方法

babel-jestの代わりに、babelを使って独自のプリプロセッサを利用する例を以下に示します。

var babel = require("babel-core");

module.exports = {
  process: function (src, filename) {
    // Allow the stage to be configured by an environment
    // variable, but use Babel's default stage (2) if
    // no environment variable is specified.
    var stage = process.env.BABEL_JEST_STAGE || 2;

    // Ignore all files within node_modules
    // babel files can be .js, .es, .jsx or .es6
    if (filename.indexOf("node_modules") === -1 && babel.canCompile(filename)) {
      return babel.transform(src, {
        filename: filename,
        stage: stage,
        retainLines: true,
        auxiliaryCommentBefore: "istanbul ignore next"
      }).code;
    }

    return src;
  }
};

このサンプルを動かすには、babel-coreパッケージをインストールする必要があります。