Module: ActiveSupport::Testing::TimeHelpers

Included in:
ActiveSupport::TestCase
Defined in:
lib/active_support/testing/time_helpers.rb

Overview

Contains helpers that help you test passage of time.

Instance Method Summary collapse

Instance Method Details

#after_teardownObject



69
70
71
72
# File 'lib/active_support/testing/time_helpers.rb', line 69

def after_teardown
  travel_back
  super
end

#freeze_time(with_usec: false, &block) ⇒ Object

Calls travel_to with Time.now. Forwards optional with_usec argument.

Time.current # => Sun, 09 Jul 2017 15:34:49 EST -05:00
freeze_time
sleep(1)
Time.current # => Sun, 09 Jul 2017 15:34:49 EST -05:00

This method also accepts a block, which will return the current time back to its original state at the end of the block:

Time.current # => Sun, 09 Jul 2017 15:34:49 EST -05:00
freeze_time do
  sleep(1)
  User.create.created_at # => Sun, 09 Jul 2017 15:34:49 EST -05:00
end
Time.current # => Sun, 09 Jul 2017 15:34:50 EST -05:00


257
258
259
# File 'lib/active_support/testing/time_helpers.rb', line 257

def freeze_time(with_usec: false, &block)
  travel_to Time.now, with_usec: with_usec, &block
end

#travel(duration, with_usec: false, &block) ⇒ Object

Changes current time to the time in the future or in the past by a given time difference by stubbing Time.now, Date.today, and DateTime.now. The stubs are automatically removed at the end of the test.

Note that the usec for the resulting time 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), unless the with_usec argument is set to true.

Time.current     # => Sat, 09 Nov 2013 15:34:49 EST -05:00
travel 1.day
Time.current     # => Sun, 10 Nov 2013 15:34:49 EST -05:00
Date.current     # => Sun, 10 Nov 2013
DateTime.current # => Sun, 10 Nov 2013 15:34:49 -0500

This method also accepts a block, which will return the current time back to its original state at the end of the block:

Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
travel 1.day do
  User.create.created_at # => Sun, 10 Nov 2013 15:34:49 EST -05:00
end
Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00


97
98
99
# File 'lib/active_support/testing/time_helpers.rb', line 97

def travel(duration, with_usec: false, &block)
  travel_to Time.now + duration, with_usec: with_usec, &block
end

#travel_backObject Also known as: unfreeze_time

Returns the current time back to its original state, by removing the stubs added by travel, travel_to, and freeze_time.

Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00

travel_to Time.zone.local(2004, 11, 24, 1, 4, 44)
Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00

travel_back
Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00

This method also accepts a block, which brings the stubs back at the end of the block:

Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00

travel_to Time.zone.local(2004, 11, 24, 1, 4, 44)
Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00

travel_back do
  Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
end

Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00


231
232
233
234
235
236
237
238
# File 'lib/active_support/testing/time_helpers.rb', line 231

def travel_back
  stubbed_time = Time.current if block_given? && simple_stubs.stubbed?

  simple_stubs.unstub_all!
  yield if block_given?
ensure
  travel_to stubbed_time if stubbed_time
end

#travel_to(date_or_time, with_usec: false) ⇒ Object

Changes current time to the given time by stubbing Time.now, Time.new, Date.today, and DateTime.now to return the time or date passed into this method. The stubs are automatically removed at the end of the test.

Time.current     # => Sat, 09 Nov 2013 15:34:49 EST -05:00
travel_to Time.zone.local(2004, 11, 24, 1, 4, 44)
Time.current     # => Wed, 24 Nov 2004 01:04:44 EST -05:00
Date.current     # => Wed, 24 Nov 2004
DateTime.current # => Wed, 24 Nov 2004 01:04:44 -0500

Dates are taken as their timestamp at the beginning of the day in the application time zone. Time.current returns said timestamp, and Time.now its equivalent in the system time zone. Similarly, Date.current returns a date equal to the argument, and Date.today the date according to Time.now, which may be different. (Note that you rarely want to deal with Time.now, or Date.today, in order to honor the application time zone please always use Time.current and Date.current.)

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), unless the with_usec argument is set to true.

This method also accepts a block, which will return the current time back to its original state at the end of the block:

Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
travel_to Time.zone.local(2004, 11, 24, 1, 4, 44) do
  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


133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/active_support/testing/time_helpers.rb', line 133

def travel_to(date_or_time, with_usec: false)
  if block_given? && in_block
    travel_to_nested_block_call = <<~MSG

Calling `travel_to` with a block, when we have previously already made a call to `travel_to`, can lead to confusing time stubbing.

Instead of:

   travel_to 2.days.from_now do
     # 2 days from today
     travel_to 3.days.from_now do
       # 5 days from today
     end
   end

preferred way to achieve above is:

   travel 2.days do
     # 2 days from today
   end

   travel 5.days do
     # 5 days from today
   end

    MSG
    raise travel_to_nested_block_call
  end

  if date_or_time.is_a?(Date) && !date_or_time.is_a?(DateTime)
    now = date_or_time.midnight.to_time
  elsif date_or_time.is_a?(String)
    now = Time.zone.parse(date_or_time)
  else
    now = date_or_time
    now = now.to_time unless now.is_a?(Time)
  end

  now = now.change(usec: 0) unless with_usec

  # +now+ must be in local system timezone, because +Time.at(now)+
  # and +now.to_date+ (see stubs below) will use +now+'s timezone too!
  now = now.getlocal

  stubs = simple_stubs
  stubbed_time = Time.now if stubs.stubbing(Time, :now)
  stubs.stub_object(Time, :now) { at(now) }

  stubs.stub_object(Time, :new) do |*args, **options|
    if args.empty? && options.empty?
      at(now)
    else
      stub = stubs.stubbing(Time, :new)
      Time.send(stub.original_method, *args, **options)
    end
  end

  stubs.stub_object(Date, :today) { jd(now.to_date.jd) }
  stubs.stub_object(DateTime, :now) { jd(now.to_date.jd, now.hour, now.min, now.sec, Rational(now.utc_offset, 86400)) }

  if block_given?
    begin
      self.in_block = true
      yield
    ensure
      if stubbed_time
        travel_to stubbed_time
      else
        travel_back
      end
      self.in_block = false
    end
  end
end