【翻訳】Jestの使い方

この記事はJestのドキュメントを和訳したものです。また、定期的に更新される予定です。

## はじめに

> Painless JavaScript Testing

Jestのホームページでは、次の3点をJestの特徴としています。

1. Easy Setup
2. Instant Feedback
3. Snapshot Testing

自分なりにまとめると、JestはJavaScriptで記述された**様々なコードを簡単にテスト**でき、テスト実行時には前回の保存から**変更があったものだけをチェックする**ように最適化されていて、**時系列に沿った状態の変遷まで可視化する**ことができる、ということでしょうか。ぜひマスターしたいものです。

## 目次
1. Getting Started
2. Snapshot Testing
3. Testing React Apps

## 1. Getting Started
以下のリンクの和訳が中心となっています。  
https://facebook.github.io/jest/docs/getting-started.html

npmを利用してインストールを行います。

```shell
$ npm install --save-dev jest
```

上述の`--save-dev`オプションを利用することで、テストを行うプロジェクトにある`package.json`にJestがリストアップされます。

それでは、二つの数字の和を求めるように設計された関数をテストしてみます。以下のように`sum.js`を作成します。

```JavaScript:sum.js
function sum(a, b) {
  return a + b;
}
module.exports = sum;
```

`module.exports`とは、コードが記述されているファイルを`module`として扱い、`exports`したオブジェクトを`require`キーワードを利用して呼び出すことができるようにするための記述です。

それでは、`sum.test.js`という名前のファイルを作成し、テストコードを書いてみましょう。

```JavaScript:sum.test.js
const sum = require('./sum');

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
})
```

上記のテストコードを実行するために、`package.json`に以下の設定を追加してください。

```package.json
"scripts": {
  "test": "jest"
}
```

それでは、登録したコマンドを実行してみましょう。

```shell
$ npm test

PASS ./sum.test.js
✔︎ adds 1 + 2 to equal 3(5ms)
```

このようにして、テストコードを作成し、実行することができます。

個人的な感想ですが、テストコードがすごく読みやすいですね。'expect sum of 1 and 2 is expected to be 3'のように、英語の文法そのものになるように設計されているみたいです。

## 2. Snapshot Testing
以下のリンクの翻訳が主となっています。  
https://facebook.github.io/jest/docs/snapshot-testing.html

### 2.1 Snapshot Testingとは？
スナップショットテストとは、作成したUIが予期せずに変化してしまわないか確認するための優れたツールのことを指します。

### Jestを利用してスナップショットテストを行う
典型的なスナップショットテストでは、レンダリングされたUIのスクリーンショットを撮り、テストコードで指定したイメージと比較して、同様であるかどうか確かめるといったことが行われます。

Reactコンポーネントのテストにおいても同様のアプローチを取ることができそうです。UIをグラフィカルにレンダリングする代わりに、テスト用のレンダラーをReactのコンポーネントツリーを対象にシリアライズ可能な形式で出力します。これにより、逐一アプリケーション全体を組み立てる必要がなくなり、リソースを消費しないテストが可能になります。

以下のようにして、`Link`コンポーネントをテストしてみましょう。  
なお、コンポーネント自体のコードは次のリンクにあります。

https://github.com/facebook/jest/blob/master/examples/snapshot/Link.react.js

```JavaScript
import React from "react";
import Link from "../Link.react";
import renderer from "react-test-renderer";

it('renders correctly', () => {
  const tree = renderer.create(
    <Link page="http://www.facebook.com">Facebook</Link>
  ).toJSON();
  expect(tree).toMatchSnapshot();
});
```

最初にこのテストコードを実行した時に、Jestは以下のような`snapshot file`を生成します。

```
exports[`Link renders correctly 1`] = `
<a
  className="normal"
  href="http://www.facebook.com"
  onMouseEnter={[Function]}
  onMouseLeave={[Function]}>
  Facebook
</a>
```

スナップショットはコードの変更に伴って生成されるでしょう。Jestでは`pretty-format`を利用し、人間が読みやすい形式でコードのレビューを示してくれます。はじめのスナップショットテスト以降、テストそのものは、その一つ手前のテストとのシンプルな比較となります。それらが合致した場合、テストは成功とみなされます。もしも合致しなかった場合は、テストランナーが修正すべきバグを通知してくれるか、スナップショットが更新される必要があることを教えてくれます。

### Updating Snapshots
バグが発見された場合に、スナップショットテストが失敗するのは明らかです。そのような場合は、ふたたびテストが通るように修正してください。

テストが成功するスナップショットそのものの更新を行いたい場合は、以下のコマンドを実行してください。

```shell
$ jest --updateSnapshot
```

## 3. Testing React Apps
> Facebookでは、ReactのテストにJestを利用しています。

個人的に一番知りたいのがReactのテストの方法でした。気合いを入れて書きます。  
また、ここでは既存のReactアプリケーションをテストする場合について書きます。

### Setup
既存のアプリケーションがある場合は、テストコードを動かすために幾つかのパッケージをインストールする必要があります。以下のコマンドを実行してください。

```shell
$ npm install --save-dev jest babel-jest babel-preset-es2015 babel-preset-react react-test-renderer
```

コマンドを実行後、`package.json`に正しくパッケージがインストールされていることを確認してください。　　
確認ができましたか？テストをする準備は万全です。

### React ComponentのSnapshot Testing
ハイパーリンクをレンダリングするLinkコンポーネントのスナップショットテストを作成しましょう。

```JavaScript:Link.react.js
import React from 'react';

const STATUS = {
  NORMAL: 'normal',
  HOVERED: 'hovered',
};

export default class Link extends React.Component {

  constructor(props) {
    super(props);

    this._onMouseEnter = this._onMouseEnter.bind(this);
    this._onMouseLeave = this._onMouseLeave.bind(this);

    this.state = {
      class: STATUS.NORMAL,
    };
  }

  _onMouseEnter() {
    this.setState({class: STATUS.HOVERED});
  }

  _onMouseLeave() {
    this.setState({class: STATUS.NORMAL});
  }

  render() {
    return (
      <a
        className={this.state.class}
        href={this.props.page || '#'}
        onMouseEnter={this._onMouseEnter}
        onMouseLeave={this._onMouseLeave}>
        {this.props.children}
      </a>
    );
  }
```

それでは、テストコードを書いていきましょう。

```JavaScript:Link.react-test.js
import React from 'react';
import Link from '../Link.react';
import renderer from 'react-test-renderer';

test('Link changes the class when hovered', () => {
  const component = renderer.create(
    <Link page="http://www.facebook.com">Facebook</Link>
  );
  let tree = component.toJSON();
  expect(tree).toMatchSnapshot();

  // manually trigger the callback
  tree.props.onMouseEnter();
  // re-rendering
  tree = component.toJSON();
  expect(tree).toMatchSnapshot();

  // manually trigger the callback
  tree.props.onMouseLeave();
  // re-rendering
  tree = component.toJSON();
  expect(tree).toMatchSnapshot();
});
```

`npm test`を実行すれば、以下のような内容のファイルを生成します。


```JavaScript:Link.react-test.js
import React from 'react';
import Link from '../Link.react';
import renderer from 'react-test-renderer';

test('Link changes the class when hovered', () => {
  const component = renderer.create(
    <Link page="http://www.facebook.com">Facebook</Link>
  );
  let tree = component.toJSON();
  expect(tree).toMatchSnapshot();

  // manually trigger the callback
  tree.props.onMouseEnter();
  // re-rendering
  tree = component.toJSON();
  expect(tree).toMatchSnapshot();

  // manually trigger the callback
  tree.props.onMouseLeave();
  // re-rendering
  tree = component.toJSON();
  expect(tree).toMatchSnapshot();
```

次回テストを実行した際には、出力されたスナップショットは、以前作成されたスナップショットと比較されます。

`jest -u`を実行することで、スナップショットを上書きすることができます。