テストを書いていると、こんな風に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
# =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じゃないのもこの理由だと思います。