カクカクしかじか

技術的なアレコレ

RSpec : 時刻のテストではtravel_toしたら必ずtravel_backしてね

追記:2019.09.23

こちらの記事で記載があるようにRails 5.2系からtravel_backを明示的に書かなくてもよくなった模様... shinkufencer.hateblo.jp

y-yagi.hatenablog.com

経緯:自分が出したプルリクだけ関係ないRSpecで落ちる

ここ数日、自分が修正したプルリクでだけ修正範囲と違うところでCIが落ちまくっていた...

なぜだろう?わからん...

時刻関係?でも... travel_to 使って書いて時間ちゃんと止めてるしなあ...

テストが落ちてた原因

Railsのテストヘルパー travel_to メソッドを beforeブロックで使用した後、afterブロックで travel_back を実行してなかった...

時刻を戻さないと(travel_backしないと)何が起こる?

一つのテストで停止した時刻が他のテスト実行時に戻されず、その他の時刻テストが落ちまくる...
なお、一見すると関係ないところで落ちているように見えるので、ランダムに落ちるテストかな?と錯覚してしまい問題に気付くのが難しい事態に...

悪い例(今回やってしまった実装)

before do
  travel_to(Time.current)
end

# ここの下にafterブロックを書いてtravel_backする必要がある

良い例

before do
  travel_to(Time.current)
end
after do
  travel_back
end

travel_toの実装を確認してみる

rails/activesupport/lib/active_support/testing/time_helpers.rb

if block_given? の分岐に入れば必ず travel_back が実行されるが、ブロックを引数に渡されないと実行されないことがここで分かります!

def travel_to(date_or_time)
    <中略>
    if block_given?
       begin
         yield
       ensure
         travel_back
     end
   end
end

自分の誤解

travel_to したらテストごとに時間が勝手にロールバックされるもんだと思ってました...
ちなみに gem 'timecop' を使った場合も時間固定した後で、afterブロックで return しないと今回の件と同じことが起こるので注意!

補足:gem 'timecop' の場合

before do
  Timecop.freeze(Time.zone.parse('2019-03-04 00:00:00'))
end
after { Timecop.return }

注意点

時刻判定テストの際は beforeブロックのtravel_to はちゃんとafterブロックで travel_back をしないとダメ!
なお、 travel_to の引数にブロックを渡した場合は、 travel_to だけで自動で travel_back が処理の最後でコールされることを忘れずに!