LoginSignup
126
75

More than 5 years have passed since last update.

RSpec で安全に Timecop する

Last updated at Posted at 2017-06-13

タイトルは釣りで、実は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 を使おう。
滅多にないけど。

126
75
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
126
75