Edited at

RSpec で安全に Timecop する

タイトルは釣りで、実はTimecopは不要という話をします。てへぺろ

Rails 4.1 以降、 ActiveSupport に travel/travel_to メソッドが実装されたのでこれを使おう。

https://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html

travel には相対時間(1日前、とか)、 travel_to には時刻(2000年1月1日とか) を指定して使う。

いずれも Timecop の freeze 相当で時間の流れは止まる。

使用するには include が必要。 rails_helper.rb に1行追加する

RSpec.configure do |config|

#...
config.include ActiveSupport::Testing::TimeHelpers
#...
end

travel しておいて travel_back し忘れると他のテストに影響してしまうため、以下のように block 内で処理すると安全。

context "leap day" do

around do |e|
travel_to('2016-2-29 10:00'.to_time){ e.run }
end
it { is_expected.to be_success }
end

to_time は 自動的にやってくれるので、実は以下のようにも書ける。可読性アップ。

    travel_to('2016-2-29 10:00'){ e.run }

ところで、 Timecop だと可能な block のネストが travel/travel_to では出来ない

http://qiita.com/akicho8/items/4b2b12786a385ef94731

という指摘があり、これに対応する issue が出されたりもしたが、最終的に Rails コミュニティが出した結論は「ネストするな」ということで、5.1.0 以降はブロックのネストで例外が発生するようになった。

https://github.com/rails/rails/pull/24890

block の中で block 無しをコールするのは許されており、その場合効果は累積される。ブロックを抜けた段階で travel_back してフルリセットされる。ブロック内で travel_back を呼んでしまうとその行以降は普通の現在時刻に戻ってしまうので注意。

context "leap day" do

around do |e|
travel_to('2016-2-29 10:00'){ e.run }
end
it do
travel -1.day
expect(Date.current).to eq '2016-2-28'.to_date
# もしここで travel_back すると
# この行は2月29日ではない
end
end

時間をずらしたうえで freeze はさせたくない場合や、時間の進み方を変えたい場合は Timecop を使おう。

滅多にないけど。