Rubyのメソッドよくわからないものが多いので、素振りします。
今回の記事では、テストの時間を固定するためのヘルパークラスであるActiveSupport::Testing::TimeHelpersを理解するために素振りしました。
※ Rubyのバージョンによっては、時刻固定後に戻す必要があったらしいです。私が検証したバージョンでは、テストごとにモック設定が消えていました。
環境
- Ruby
- 3.0.2p107
- Rails
- 6.0.3.7
- RSpec
要約
freeze_timeメソッド
現在時刻で時刻を固定するためのメソッド。基本的には、特定時刻に発生する障害を見逃すといけないので、こちらのメソッドを使用した方がよいと考えています。
travel_toメソッド
指定した時刻で時刻を固定するためのメソッド。うるう年等の特別な日付のテストをするときにはこちらのメソッドを使用しましょう。DBの更新時刻等もテストしたい場合は、こちらで固定する必要があります。
下準備
デフォルトでは、ActiveSupport::Testing::TimeHelpersは使えません。
spec_helper.rbにActiveSupport::Testing::TimeHelpersを設定して、使用できるようにします。
RSpec.configure do |config| require 'active_support/testing/time_helpers' config.include ActiveSupport::Testing::TimeHelpers end
挙動確認
freeze_timeメソッド
現在時刻で時刻を固定するためのメソッド。基本的には、特定時刻に発生する障害を見逃すといけないので、こちらのメソッドを使用した方がよいと考えています。
テストでは、freeze_timeを使用する場合は値が等しくなりますが、freeze_timeを使用しない場合は値が異なることを確認しています。
context 'freeze_time' do
before { freeze_time }
it "同一作成時間になること" do
expect(Time.now).to eq(Time.now)
end
end
context 'なにもしない' do
it "同一作成時間にならないこと" do
expect(Time.now).not_to eq(Time.now)
end
end
travel_toメソッド
指定した時刻で時刻を固定するためのメソッド。うるう年等の特別な日付のテストをするときにはこちらのメソッドを使用しましょう。DBの更新時刻等もテストしたい場合は、こちらで固定する必要があります。
テストでは現在時刻を2020/01/01で固定して、時間が一致することを確認しています。
また、travelメソッドという時刻をずらせるメソッドがあります。単体観点では使うことはありませんが、結合観点で1回目実行時から1週間、1ヵ月、1年後に再実行したときの処理を確認したいときに使えそうでした。「排他ロックをテーブルレコードで行うタイプの制御しているときに、更新時刻がN日経過未満であればエラーとして扱う。更新時刻がN日経過していたら強制的に排他ロックを上書きできるようにする」みたいな処理の確認もできそうです。
context 'travelとtravel_to' do
let(:_20200101) { Time.new(2020, 1, 1, 0, 0, 0) }
let(:_20210101) { Time.new(2021, 1, 1, 0, 0, 0) }
before { travel_to(_20200101)}
it "同一作成時間になること" do
expect(Time.now).to eq(Time.now)
end
it "travelして時間をずらすこと" do
now = Time.now
travel 1.years
now2 = Time.now
expect(now).not_to eq(now2)
expect(now).to eq(_20200101)
expect(now2).to eq(_20210101)
end
end
処理呼び出し回数によって、時刻を変更したい
ActiveSupport::Testing::TimeHelpersでは呼び出し回数によって、時刻を変更するということは難しそうです。
ユースケースとしては、「日付を元にして主キーを生成したいが、テスト時に時刻が常に同一だと主キー制約違反が発生してテストができない」ということを想定しています。
describe "呼び出す回数ごとに処理する内容を変更する" do
let(:_20200101) { Time.new(2020, 1, 1, 0, 0, 0) }
let(:_20210101) { Time.new(2021, 1, 1, 0, 0, 0) }
before do
allow(Time).to receive(:now)
.and_return(_20200101, _20210101)
end
it "1回目、2回目以降は引数の違いで異なる値が返却される。3回目と4回目は一致していること" do
expect(Time.now).not_to eq(Time.now)
expect(Time.now).to eq(Time.now)
end
end
ソースコード
- https://github.com/hirotoKirimaru/ror-practice/blob/b919cd1b6b809afe751f1f69e16dc427c91edd11/sandbox/spec/models/time_mock_spec.rb
- https://github.com/hirotoKirimaru/ror-practice/blob/cdd1c08c1035d3e845679ceb9ea1ff741f0dfcb3/sandbox/spec/spec_helper.rb
終わりに
Javaと違って簡単に時刻の固定ができて羨ましいです。
Rubyはこういうところが便利でいいですね。
この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。
参考情報
- ActiveSupport::Testing::TimeHelpers
- Rails6 のちょい足しな新機能を試す24(unfreeze_time 編) - Qiita
- 使えるRSpec入門・その3「ゼロからわかるモック(mock)を使ったテストの書き方」 - Qiita
類似情報
https://nainaistar.hatenablog.com/entry/2022/02/21/120000
