travel_to はミリ秒以下の情報を切り捨てる
次のように渡した時刻のミリ秒以下の精度の情報は切り捨てられる。
[1] pry(main)> require 'active_support/testing/time_helpers'
=> true
[2] pry(main)> include ActiveSupport::Testing::TimeHelpers
=> Object
[3] pry(main)> t = Time.now
=> 2021-12-01 17:32:59.691290907 +0000
[4] pry(main)> t.to_f
=> 1638379979.6912909
[5] pry(main)> travel_to t
=> nil
[6] pry(main)> Time.now
=> 2021-12-01 17:32:59 +0000
[7] pry(main)> Time.now.to_f
=> 1638379979.0 # <== [4] と比較して小数部がなくなっている
実際以下で usec を 0 に設定している。
now = date_or_time.to_time.change(usec: 0)
この挙動は ドキュメント にも明示されている仕様。
Note that the usec for the time passed will be set to 0 to prevent rounding errors with external services, like MySQL (which will round instead of floor, leading to off-by-one-second errors).
今回自分が嵌ったのは、MySQL にミリ秒以下の精度の datetime を格納するよう意図して定義したカラムのテストで travel_to がうまく使えなかったというケースだった。
- 前述のドキュメントにもあるように MySQL の DATETIME 型のデフォルトは秒単位の精度だが、それ以上の精度を
DATETIME(6)
などとして指定できる (5.6.4 以降らしい) - Rails の場合 migration ファイルに
precision
というオプションをつければ良い
t.datetime :your_column, precision: 6
- 今回は
Time.now
にだけ依存する処理のテストだったので、次のようにここだけをピンポイントに差し替えて対処した- travel_to がやっているように他のケースも stub する必要がある場合はもっと複雑になる
allow(Time).to receive(:now).and_return(t)
[1] pry(main)> require 'rspec/mocks/standalone'
=> true
[2] pry(main)> t = Time.now
=> 2021-12-01 17:59:01.306662808 +0000
[3] pry(main)> t.to_f
=> 1638381541.3066628
[4] pry(main)> allow(Time).to receive(:now).and_return(t)
=> #<RSpec::Mocks::MessageExpectation #<Time (class)>.now(any arguments)>
[5] pry(main)> t2 = Time.now
=> 2021-12-01 17:59:01.306662808 +0000
[6] pry(main)> t2.to_f
=> 1638381541.3066628 # <== [3] と同様の結果になっている
PR
すがわら まさのり (著), 前島 真一 (著), 橋立 友宏 (著), 五十嵐 邦明 (著), 後藤 優一 (著) 形式: Kindle版