Rails の travel_to で秒未満も固定する

に公開

特定のテストで『テスト時はこの時刻で固定する』みたいな検証をしたいことがたまにあります。
そういうときに ActiveSupport::Testing::TimeHelperstravel_to 等が利用できます。
travel_to は『ブロック内の時間を引数の時間で固定する』という機能になります。

freeze_time = Time.parse("2025-03-14 12:34:56")

travel_to(freeze_time) do
  # このブロック内では常に freeze_time の時刻になる
  pp Time.now # => 2025-03-14 12:34:56 +0900

  sleep 3

  # 3秒経っていてたとしても同じ時刻が返ってくる
  pp Time.now # => 2025-03-14 12:34:56 +0900
end

基本的なテストをする場合はこれで問題ないと思います。

秒未満まで固定できない

travel_to ではデフォルトでは秒未満の数値までは固定されません。
なので厳密な時刻を扱う世界では意図するテストにならないケースがあります。

# Time.now は秒未満まで含まれている
freeze_time = Time.now
pp freeze_time
# => 2025-02-21 20:09:29.639692144 +0900

travel_to(freeze_time) do
  # freeze_time は秒未満の数値まで含まれているが秒単位までしか固定されない
  # なので Time.now は travel_to を呼び出すよりも『過去の時刻』になってしまうケースがある
  pp Time.now
  # => 2025-02-21 20:09:29 +0900
end

このように 未来の時刻 -> 過去の時刻 に遡ってしまうケースがありえてしまいます。

with_usec: を利用する

Rails 7.1 から追加された with_usec: オプションを利用すると固定される時刻に『秒未満』の数値も含まれるようになります。

# Time.now は秒未満まで含まれている
freeze_time = Time.now
pp freeze_time
# => 2025-02-21 20:13:50.86102677 +0900

# with_usec: true で秒未満も固定されうようになる
travel_to(freeze_time, with_usec: true) do
  pp Time.now
  # => 2025-02-21 20:13:50.86102677 +0900
end

これで変に 未来 -> 過去 に時間を遡ることもなくなりますね。

GitHubで編集を提案

Discussion