sinon.jsのuseFakeTimersを使った時間制御とvowsを使う際の書き方

nodeで時間依存なテストコードを書きたい場合、偽のタイマーを使って時間制御する
サンプル読んで本当にそんなことができんのかい、と疑問だったので、最小コードだけで挙動だけ確認したメモ

いれる

npm install sinon

偽のタイマーを使う

>sinon = require("sinon")
>clock = sinon.useFakeTimers()

ここで注意するべきなのは sinon#useFakeTimers()した時点でDateクラスは上書きされ、unixtimeで起点となるGMT 1970年1月1日にリセットされる。明示的にclock#tickを呼び出さない限り、この時間は動かない。

>now = new Date()
Thu, 01 Jan 1970 00:00:00 GMT

基準時はコンストラクタで Dateインスタンスを渡せば任意の時間にリセットすることが可能

時間を進める

clock#tickで60秒進めてみる。

> clock.tick(1000*60)
> new Date()
Thu, 01 Jan 1970 00:01:00 GMT

午前0時1分にまで進んだ。

元に戻す

clock#restoreを呼ぶ

>clock.restore()
>new Date
Sun, 15 Jan 2012 20:12:16 GMT

無事現在の時刻に戻った。

VowsでuseFakeTimersを使う際の注意

Vowsで同じバッチの中の処理は並列実行されてしまう。
Dateオブジェクトというグローバルなオブジェクトをいじってる以上、ちゃんとrestoreしてから次のテストにうつらないと失敗する。
teardownでちゃんと後処理をしましょう。

以下CoffeeScript
単体で実行してる場合(大丈夫な例)

sinon = require 'sinon'
vows.describe('時間経過のテスト').addBatch
  '30分経過させる':
    topic : ->
      @clock = sinon.useFakeTimers()
      return new Date

    "経過させた時間は元の時間と異なる":(topic)->
      @clock.tick 30*60*1000
      assert.notEqual topic , new Date

    teardown:->
      @clock.restore()


だめな例

sinon = require 'sinon'
vows.describe('時間経過のテスト').addBatch
  '1秒経過させる':
    topic : ->
      @clock = sinon.useFakeTimers()
      new Date

    "時間経過を確認":(topic)->
      @clock.tick 1000
      assert.equal (new Date).getTime-topic.getTime() , 1000

    teardown:->
      @clock.restore()

  '1秒経過させる(並列に実行される)':
    topic : ->
      @clock = sinon.useFakeTimers()
      new Date

    "時間経過を確認":(topic)->
      @clock.tick 1000
      assert.equal (new Date).getTime-topic.getTime() , 1000

    teardown:->
      @clock.restore()

逐次実行するように分割

vows.describe('時間経過のテスト')
.addBatch
  '1秒経過させる':
    topic : ->
      @clock = sinon.useFakeTimers()
      new Date

    "時間経過を確認":(topic)->
      @clock.tick 1000
      assert.equal (new Date).getTime-topic.getTime() , 1000

    teardown:->
      @clock.restore()

.addBatch
  '1秒経過させる(逐次)':
    topic : ->
      @clock = sinon.useFakeTimers()
      new Date

    "時間経過を確認":(topic)->
      @clock.tick 1000
      assert.equal (new Date).getTime-topic.getTime() , 1000

    teardown:->
      @clock.restore()