August 10, 2017

JestのSnapshotテスト

ReactのテストとしてJestでSnapshotテストを行うときのメモ。
ボコボコDOMが変化するコンポーネントのテストは非常に面倒なものだが、JestのSnapshotを使えばサクッとできて便利。

// Example.react.js
import React, { Component } from 'react';

export default class Example extends Component {
  constructor() {
    super();
  }

  render() {
    return (
      <div>
        <p>text</p>
      </div>
    )
  }
}

この単純なExampleコンポーネントのスナップショットを取るテストコードは以下のようになる。
toMatchSnapshotするだけ。便利。

// example.react.test.js
'use strict';

import React from 'react';
import Example from '../Example.react';
import renderer from 'react-test-renderer';

describe('Example', () => {

  it('レンダリングができること', () => {
    const tree = renderer.create(
      <Example />
    ).toJSON();
    expect(tree).toMatchSnapshot();
  });
});

Jestを実行する。

$ jest __tests__/example.react.test.js
/node_modules/.bin/jest __tests__/example.react.test.js
 PASS  __tests__/example.react.test.js
  Example
    ✓ レンダリングができること (11ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   1 passed, 1 total
Time:        0.263s, estimated 1s
Ran all test suites matching "__tests__/example.react.test.js".

このとき__tests__/__snapshots__/example.react.test.js.snapが生成される。

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Example レンダリングができること 1`] = `
<div>
  <p>
    text
  </p>
</div>
`;

このようにレンダリング結果が保存される。次回以降のテストではこのスナップショットと比較され、一致すればPassingする。
試しにExample.react.jsを変更してみる。

import React, { Component } from 'react';

export default class Example extends Component {
  constructor() {
    super();
  }

  render() {
    return (
      <div>
        <p>Hello</p>
      </div>
    )
  }
}

textHelloに変更したのでレンダリング結果は前回のスナップショットと異なるため、失敗する。

$ jest __tests__/example.react.test.js
 FAIL  __tests__/example.react.test.js                                                                                                                                                                      [7/697]
  ● Example › レンダリングができること

    expect(value).toMatchSnapshot()

    Received value does not match stored snapshot 1.

    - Snapshot
    + Received

     <div>
       <p>
    -    text
    +    Hello
       </p>
     </div>

      at Object.<anonymous> (__tests__/example.react.test.js:13:18)
          at Promise (<anonymous>)
      at Promise.resolve.then.el (node_modules/p-map/index.js:42:16)
          at <anonymous>

  Example
    ✕ レンダリングができること (14ms)

Snapshot Summary
 › 1 snapshot test failed in 1 test suite. Inspect your code changes or re-run with `-u` to update them.

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   1 failed, 1 total
Time:        0.276s, estimated 1s
Ran all test suites matching "__tests__/example.react.test.js".

このようにコンポーネントを変更し、最新のスナップショットを撮り直すにはjest --updateSnapshotすれば良い。

onClickのようにイベントが発生した際にDOMが変化する際は、その都度スナップショットを取る。

ボタンをクリックするとカウントアップするやつ。

import React, { Component } from 'react';

export default class Example extends Component {
  constructor() {
    super();

    this._onClick = this._onClick.bind(this);

    this.state = {
      count: 0
    }
  }

  _onClick() {
    this.setState( { count: this.state.count + 1 } );
  }

  render() {
    return (
      <div>
        <p>{this.state.count}</p>
        <button onClick={this._onClick} >Click</button>
      </div>
      )
  }
}

ここでは雑にonClickを呼ぶ。
Enzymeと併用したほうが幸せになれると思う(jest-serializer-enzymeというものもある)。

'use strict';

import React from 'react';
import Example from '../Example.react';
import renderer from 'react-test-renderer';

describe('Example', () => {

  it('レンダリングができること', () => {
    const tree = renderer.create(
      <Example />
    ).toJSON();
    expect(tree).toMatchSnapshot();
  });

  it('onClick', () => {
    const component = renderer.create(
      <Example />
    )
    // 1回目クリック
    component.getInstance()._onClick();
    expect(component.toJSON()).toMatchSnapshot();
    // 2回目クリック
    component.getInstance()._onClick();
    expect(component.toJSON()).toMatchSnapshot();
  });

});
$ jest __tests__/example.react.test.js
 PASS  __tests__/example.react.test.js
  Example
    ✓ レンダリングができること (12ms)
    ✓ onClick (6ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   3 passed, 3 total
Time:        0.266s, estimated 1s
Ran all test suites matching "__tests__/example.react.test.js".

以下のスナップショットが生成される。

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Example onClick 1`] = `
<div>
  <p>
    1
  </p>
  <button
    onClick={[Function]}
  >
    Click
  </button>
</div>
`;

exports[`Example onClick 2`] = `
<div>
  <p>
    2
  </p>
  <button
    onClick={[Function]}
  >
    Click
  </button>
</div>
`;

exports[`Example レンダリングができること 1`] = `
<div>
  <p>
    0
  </p>
  <button
    onClick={[Function]}
  >
    Click
  </button>
</div>
`;
このエントリーをはてなブックマークに追加

© Kouhei Morita 2018