Skip to content

Commit

Permalink
Allow subsecond resolution in travel_to helper
Browse files Browse the repository at this point in the history
The `travel_to` helper didn’t support subsecond time traveling in order
to prevent problems with external services, such as MySQL. This,
however, makes it impossible to test features that use durations less
than a second ("subsecond").

This therefore allows `travel_to` to stub `Time.now` with the accurate
time passed to it as `date_or_time` if the `with_usec` optional argument
is set to true:

    Time.now.inspect # => 2022-01-05 22:36:54.613371959 +0000
    travel_to(0.1.seconds.from_now, with_usec: true)
    Time.now.inspect # => 2022-01-05 22:36:54.713371959 +0000
  • Loading branch information
KevSlashNull committed Jan 5, 2022
1 parent ce2ee21 commit 0ab5a3a
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 3 deletions.
9 changes: 6 additions & 3 deletions activesupport/lib/active_support/testing/time_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ def travel(duration, &block)
#
# 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).
# leading to off-by-one-second errors), unless the <tt>with_usec</tt> argument
# is set to <tt>true</tt>.
#
# This method also accepts a block, which will return the current time back to its original
# state at the end of the block:
Expand All @@ -125,7 +126,7 @@ def travel(duration, &block)
# Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
# end
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
def travel_to(date_or_time)
def travel_to(date_or_time, with_usec: false)
if block_given? && in_block
travel_to_nested_block_call = <<~MSG
Expand Down Expand Up @@ -158,12 +159,14 @@ def travel_to(date_or_time)
now = date_or_time.midnight.to_time
elsif date_or_time.is_a?(String)
now = Time.zone.parse(date_or_time)
elsif with_usec
now = date_or_time.to_time
else
now = date_or_time.to_time.change(usec: 0)
end

stubbed_time = Time.now if simple_stubs.stubbing(Time, :now)
simple_stubs.stub_object(Time, :now) { at(now.to_i) }
simple_stubs.stub_object(Time, :now) { at(now.to_f) }
simple_stubs.stub_object(Date, :today) { jd(now.to_date.jd) }
simple_stubs.stub_object(DateTime, :now) { jd(now.to_date.jd, now.hour, now.min, now.sec, Rational(now.utc_offset, 86400)) }

Expand Down
35 changes: 35 additions & 0 deletions activesupport/test/time_travel_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,41 @@ def test_time_helper_travel_to_with_subsequent_calls
end
end

def test_time_helper_travel_to_with_usec
Time.stub(:now, Time.now) do
duration_usec = 0.1.seconds
traveled_time = Time.new(2004, 11, 24, 1, 4, 44) + duration_usec
expected_time = Time.new(2004, 11, 24, 1, 4, 44)

assert_nothing_raised do
travel_to traveled_time

assert_equal expected_time, Time.now

travel_back
end
ensure
travel_back
end
end

def test_time_helper_travel_to_with_usec_true
Time.stub(:now, Time.now) do
duration_usec = 0.1.seconds
expected_time = Time.new(2004, 11, 24, 1, 4, 44) + duration_usec

assert_nothing_raised do
travel_to expected_time, with_usec: true

assert_equal expected_time.to_f, Time.now.to_f

travel_back
end
ensure
travel_back
end
end

def test_time_helper_travel_with_subsequent_block
Time.stub(:now, Time.now) do
outer_expected_time = Time.new(2004, 11, 24, 1, 4, 44)
Expand Down

0 comments on commit 0ab5a3a

Please sign in to comment.