他のテストがタイムトラベルに巻き込まれないよう確実に Timecop.return する(for RSpec)
テストコード内で Timecop.return
し忘れると、後続のテストでも Timecop.freeze
や Timecop.travel
によるタイムトラベルを続行してしまうため、正常な時刻に戻らず、全く関係のないテストが落ちることがある。
例えば、以下のように Timecop.return
を記述し忘れるケースがある。
今日これに遭遇してしまったので、Timecop.return
するいくつかの方法を知識整理も兼ねてまとめてみたいと思う。
describe Foo do before do Timecop.freeze(2000, 10, 10, 9, 0, 0) end it { some_code } end
一番単純な方法は、after ブロックで return することである。これで example 実行後にちゃんと時間がもとに戻る。
describe Foo do before do Timecop.freeze(2000, 10, 10, 9, 0, 0) end after do Timecop.return end it { some_code } end
あるいは、メソッドブロックで freeze させたいコード片を囲っても良い。こっちのほうが Ruby らしい。
describe Foo do it do Timecop.freeze(2000, 10, 10, 9, 0, 0) { some_code } end end
このようなイケてる shared context を定義すれば、spec から Timecop を隠蔽できる。美しい。
RSpec.shared_context 'with frozen time' do |options| let(:frozen_time) { options[:at] } if options && options[:at] around do |e| Timecop.freeze(frozen_time) { e.run } end end
describe Foo do include_context 'with frozen time', at: Time.current it { some_code } end
このように Timecop.return
する方法はいくつかあるが、今回は example 実行後に必ず Timecop.return
を呼び出す Helper を作った。
module RSpec module TimecopReturnHelper def self.included(rspec) rspec.after(:each) do |_| Timecop.return end end end end
RSpec.configure do |config| config.include RSpec::TimecopReturnHelper end
これによって、他のテストのタイムトラベルに巻き込まれることを確実に防げるし、二度と Timecop.return
を書き忘れない。
spec 側ですでに Timecop.return
している場合は、二重に Timecop.return
への呼び出しが発生するが、これによってテスト時間が延びることは今のところなさそうである。