きり丸の技術日記

技術検証したり、資格等をここに残していきます。

Rubyでテストの時間を固定させる(TimeHelpersとその他)

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.rbActiveSupport::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

ソースコード

終わりに

Javaと違って簡単に時刻の固定ができて羨ましいです。

Rubyはこういうところが便利でいいですね。


この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。

参考情報

類似情報

https://nainaistar.hatenablog.com/entry/2022/02/21/120000

f:id:nainaistar:20211227225858p:plain