Lance Pollard
Lance Pollard

Reputation: 79390

Override what Ruby thinks is the current time in Time.now?

I want to create test data for an application, and there are a lot of time_at attributes being tracked, too many to override in a maintainable way. What I'm thinking is, can I just change the base reference time variable in Ruby?

This would make it so created_at, updated_at, last_login_at, etc., could be set to an artificial time, so I could do this in tests:

Date.today #=> Thu, 30 Dec 2010
Time.system_time_offset = 1.week.ago # made up
Date.today #=> Thu, 23 Dec 2010
Time.now   #=> Thu Dec 23 14:08:38 -0600 2010

user_1 = User.create!
user_1.created_at #=> Thu Dec 23 14:08:38 -0600 2010

Time.reset_system_time # made up

user_2 = User.create!
user_1.created_at #=> Thu Dec 30 14:08:38 -0600 2010

Is there a way to do this?

Upvotes: 4

Views: 4105

Answers (5)

Nate914375
Nate914375

Reputation: 192

A good gem for this is Timecop: https://github.com/travisjeffery/timecop.
You can freeze time or change the time (while it continues to progress) very easily.

Ex.

Time.now
# => 2014-03-14 13:17:02 -0400
Timecop.travel 2.hours.ago
Time.now
# => 2014-03-14 11:17:04 -0400

Its nicer than the mocha solution since all time functions will be affected equally, so you won't have a test where Time.now is returning something different then DateTime.now

Its also more up-to-date than the time-warp gem suggested in another answer.

Upvotes: 5

Steven Cummings
Steven Cummings

Reputation: 120

I use the timewarp gem for this sort of thing. You just put your code in a pretend_now_is(time) block and the code inside will be executed as if that was the actual time.

http://github.com/harvesthq/time-warp

Here's an example

def test_should_find_company_needing_reminded_today
  pretend_now_is(Time.utc(2008,"jul",24,20)) do #=> Thu Jul 24 20:00:00 UTC 2008
    @company.reminder_day = 'Thursday'
    @company.save
    companies = Company.find_companies_needing_reminded_today
    assert_equal true, companies.include?(@company)
  end
end

Upvotes: 3

Brian Clapper
Brian Clapper

Reputation: 26220

It's also possible to (yuck) monkeypatch Time:

$start = Time.now - 86400 # this time yesterday

class Time
  class << Time
    def new
      $start
    end
    def now
      Time.new
    end
  end
end

puts(Time.now)
puts($start)

Upvotes: 1

Bob Aman
Bob Aman

Reputation: 33239

Honestly, I usually write tests for current time to check if the timestamp is within a reasonable range. i.e., check if the timestamp is greater than 1.minute.ago. Changing the system clock is likely to have all kinds of unpredictable side-effects, so you don't want to do that. You might be able to track down all the places in Ruby where the current time is accessed (though I think most methods just use Time.now) and monkey-patch them for the tests, but I'd probably still prefer just checking the timestamp is within a sane range.

Upvotes: 1

Pan Thomakos
Pan Thomakos

Reputation: 34350

You could use Mocha to change the return value of Time.now during a test:

Time.stubs(:now).returns(Time.now - 1.day)

Upvotes: 7

Related Questions