jestのrunAllTimers/runOnlyPendingTimers

2015-04-12T00:00:00+09:00 JavaScript

参考1: https://facebook.github.io/jest/docs/timer-mocks.html

参考2: http://qiita.com/koba04/items/2f97904b3ca247fc1917

以前に出した上記の参考資料2にも書いてるけど

runAllTimersは全てのTimerで待っている処理を実行させて、runOnlyPendingTimersはその時点でPendingになっているものだけを実行します。 setTimeoutで再帰しているような実装の場合、runAllTimersを使ってしまうと無限ループになるのでその時はrunOnlyPendingTimersを使って1つずつ進めながらテストを書いていきます。

っていう事で検証してみた

シンプルにsetTimeoutするパターン

var React = require("react");

var Timer = React.createClass({
  getInitialState: function() {
    return { text: "" };
  },
  componentDidMount: function() {
    setTimeout(function() {
      this.setState({ text: "hello" });
    }.bind(this), 2000);
  },
  render: function() {
    return (
      <span>{this.state.text}</span>
    );
  }
});

module.exports = Timer;

っていう感じでcomponentDidMountでsetTimeoutしてそこからsetStateするようなパターンとかであればrunAllTimersを呼び出す事でそこの部分をテストする事が可能

jest.dontMock("../Timer");

var React = require("react/addons"),
    TestUtils = React.addons.TestUtils;

describe("Timer", function() {
  it("call render", function() {
    var Timer = require("../Timer");
    var timer = TestUtils.renderIntoDocument(<Timer />);

    // runAllTimersを呼び出す前にやるとsetTimeoutが終わってないはずなので
    // setStateされない為にテストはfailする
    // expect(timer.state.text).toEqual("hello");

    jest.runAllTimers();
    expect(timer.state.text).toEqual("hello");
  });
});

まぁシンプルなのでこんくらいやれば分かると思うんで終わり

setTimeoutが再帰する場合

var React = require("react");

var Timer = React.createClass({
  getInitialState: function() {
    return { text: "" };
  },
  componentDidMount: function() {
    this.loop();
  },
  render: function() {
    return (
      <span>{this.state.text}</span>
    );
  },
  loop: function() {
    this.setState({ text: "hello " + new Date().getTime() });
    setTimeout(this.loop, 1000);
  }
});

module.exports = Timer;
  • componentDidMountでloopを呼び出す
  • そこでまずsetStateして
  • 同メソッドをsetTimeoutで呼び出すように再帰する事で定期レンダリングみたいな事する

ような場合、上記でも書いたようにrunAllTimersを使うと無限ループしてテストが終わらない。そういうケースにおいてはrunOnlyPendingTimersを使ってタイマースタック?を一個づつ進めていきながらテストする

jest.dontMock("../Timer");

var React = require("react/addons"),
    TestUtils = React.addons.TestUtils;

describe("Timer", function() {
  it("call render", function() {
    var Timer = require("../Timer");
    var timer = TestUtils.renderIntoDocument(<Timer />);

    jest.runOnlyPendingTimers();
    console.log("text: " + timer.state.text);

    jest.runOnlyPendingTimers();
    console.log("text: " + timer.state.text);
  });
});

ケースが微妙なだけに検証するようなコード書いてないけど、テスト実行すると

text: hello 1428830029317
text: hello 1428830029320

っていうような結果が得られる。まぁ上記で書いてるように再帰するようなケースの場合にはrunOnlyPendingTimersを使って一つづつ処理を行いながら検証していくような感じかと。まぁ今回はReact Componentに全て書いちゃってるのであれなんですが、jest.genMockFunctionなりを使いつつタイマーがコールバックを呼び出したか等までの検証は必要かと思われる

とりあえずはjestなドキュメント読みなネタは今回で終了。まぁ一応ドキュメントとか読みながら使ってみた感想としては非常に良いテスト環境だとは思うんですが幾分テストの実行速度等が若干遅めなのがちょいと気になる所なので、今後それが解決されていけば良いなっていう思う所(自分の検証環境が悪いのかどうかは分からないけど)。でもモックオブジェクトとか利用してテストするっていう前提であればすごい使えるんじゃないかなと

react-routerのテストに関して jestを使ってReact.jsなスクリプトをテストする