テストで、Timeオブジェクト同士を比較するときのメモ

テストを書いていると、こんな風にTimeオブジェクト同士を比較する時があります。

  assert @user.save
  assert_equal Time.now, @user.reload.updated_at


まぁ、updated_atはrails側でもテストしているのでやる方はあまりいないかもしれませんが、自分でも「evaluated_at」とかを作ることもあるんで、こういうテストも結構多いのでは。

Timeオブジェクト同士の比較のテストで落ちることが多い

多いのはこれ。

<Wed May 21 01:20:15 +0900 2008> expected but was
<Wed May 21 01:20:16 +0900 2008>.


1秒差で落ちる。

解決策

Time.freeze_timeを使おう!

  • freeze.rb

http://funny-code.googlecode.com/svn/trunk/vendor/plugins/active_project/lib/active_project/ruby_ext/time/freeze.rb

# =Adds +time_freeze+ block to Time class
            #
            # Everything that is executed inside the freeze_time block executes with Time.now returning a set value.
            # Don't worry about @expected.date being different than the time on the new message.
            #
            # 	Time.freeze_time do 
            # 		@user = users(:mike)
            # 		@expected.date = Time.now
            #		assert_equal @expected.encoded, UserMailer.create_new_comment(@user).encoded    
            #	end


これを使うと、ブロック内では時間が止まる(Time.nowが一定の時間を返す)ので、先ほどのテストはこんな感じにすればおk。

Time.freeze_time do
  assert @user.save
  assert_equal Time.now, @user.reload.updated_at
end

ちょっと注意

Time.freeze_timeはTime.nowのデフォルト値がTime.nowじゃない。

def freeze_time(frozen_time = 946702800)


なので、こう書いてみる。

Time.freeze_time(Time.now) do
  assert @user.save
  assert_equal Time.now, @user.updated_at
end


でも、これは落ちることがあります。

<Wed May 21 01:20:15 +0900 2008> expected but was
<Wed May 21 01:20:15 +0900 2008>.


これが何故落ちるのかはこちら。
Timeオブジェクトの等価でちょいはまった - Slow Dance
簡単に言えば、ミリ秒の差で落ちる。freeze時のTime.nowのミリ秒の部分は0とは限らないのですが、@user.reload.updated_atはキャスト時にミリ秒の部分が0になる。その差で落ちる。


この場合は、to_sとかしてから比較するのがいいかも。

  assert_equal Time.now.to_s, @user.updated_at.to_s


それか、専用のメソッド作るとか。

def assert_now(time)
  assert_equal Time.now.to_s, time.to_s
end


そもそも、Time.freeze_timeの初期値がTime.nowじゃないのもこの理由だと思います。