Close
Glad You're Ready. Let's Get Started!

Let us know how we can contact you.

Thank you!

We'll respond shortly.

LABS
Til the End of Time (or Time.now)

Sooner or later, every test-driven developer discovers that they need a superpower – the power to control time. Let’s say you’re working on a scheduling system. You’re going to want to write tests that say, “Assume that it’s 2PM on January 3rd and this user does THIS. What happens as a result? What happens at 4PM on January 3rd as a result?”

One of the joys of writing in Rails is the sheer power you have to change your application universe at almost any level you choose. As a result, we’ve come up with a variety of ways to solve this problem. As it turns out, with great power comes great responsibility. (I’m not sure why I’m going all comic book right now, but let’s run with it).

Stubbing Time

One of the cooler features of the latest generation of mocking frameworks like FlexMock and Mocha is the ability to stub any method you choose and replace its functionality with a new one of your choosing.. and then the framework will clean up after you when the test is over!

This leads us to some intriguing possibilities such as the following:

def test_party
  Time.stubs(:now).returns(Time.local(1999, 'Jan', 1))
  people(:nathan).party
  assert people(:nathan).very_drunk?
end

and for the duration of the test, Rails will decide that it needs to party like it’s 1999. Sounds great, right?

Well, there’s just a teeny catch.

Let’s try benchmarking the test_party method.

Benchmark.measure do |b|
  ... test_party
end

And you’ll discover that this test takes -2000000 seconds to run!

Here’s another problem area:

def test_partying_in_selenium
  Time.stubs (:now).returns(Time.local(1999, 'Jan', 1))
  click "link=Party!"
  wait_for {get_text("id=sobriety") == "drunk"}
end

The issue with this method is that the wait_for method relies on Time.now to time out.. and Time has now been mocked out for the duration of the test. So we end up with a Selenium test that takes forever.

So doing a mass Time.now stub will work, but stubs out the test framework along with your application. Not a great solution.

How about this?

A Time wrapper for your application

The idea here is that we define a time wrapper class that we use for our application.. let’s call it Clock. The idea of the Clock object is that the application refers to the Clock every time it needs to know what time it is. Clock would have a very similar API to Time.. we can use Clock.now instead of Time.now, and switching over is as simple as a global search and replace.

Clock.now does exactly the same thing as Time.now… except when you’re running tests. When you run tests, you suddenly get access to clock-controlling methods, and can change what the application’s sense of ‘now’ is for the duration of a test.

Here’s a simple implementation of Clock:

in lib/clock.rb:

class Clock
  def self.now
     Time.now
  end
end

in mocks/test/lib/clock.rb:

class Clock
  def self.now
     @@now ||= Time.now
  end

  def self.now=(new_time)
     @@now = new_time
  end
end

To finish off, here’s example test code that uses this:

def test_party
  Clock.now = Time.local(1999, 'Jan', 1)
  people(:nathan).party
  assert people(:nathan).very_drunk?
end

def teardown
  Clock.now = Time.now
end

Some other benefits to this approach is that you have a handy place to add fancier setters to Clock. You can use methods like Clock.tick(2.hours), Clock.advance_to_midnight, et cetera. The actual implementations of these methods would be very simple, and I’ll leave them as an exercise for the reader who wants to use them.

Here’s some issues you’ll run into, though:

  • The implementation I wrote above doesn’t automatically tear down the Clock time override after your test is done. As a result, you’ll have to do this teardown yourself. If you don’t, later tests will think that Clock.now is whatever the previous tests say it is, and you may end up with fragile interactions between tests as a result. Very bad.
  • Rails has many lower-level methods that use Time.now directly, and these methods don’t easily switch over to Clock.now. Among the more interesting ones are the created_at and updated_at methods, and constructs like 3.days.from_now and 5.hours.ago. So, you’d need workarounds to test these methods.

Here’s a case that would work:

def Clock.ago(duration)
  Clock.now - duration
end

def Clock.from_now(duration)
  Clock.now + duration
end

Using stubs rather than a separate mock clock class

Why do we need to create an ‘Clock.now=XXX’ method, anyways? If we use stubs to override what AppTime.now returns, we can let the test framework clean up after itself when the test is complete. In addition, we can drop the mock definition entirely.. a very good thing indeed.

So in this universe, we’d end up with something like this:

Here’s a simple implementation of Clock:

in lib/clock.rb:

class Clock
  def self.now
     Time.now
  end
end

in mocks/test/lib/clock.rb: nothing!! this file doesn’t exist!

Test code:

def test_party
  Clock.stubs(:now).returns(Time.local(1999, 'Jan', 1))
  people(:nathan).party
  assert people(:nathan).very_drunk?
end

This approach has the advantage of reducing the number of ‘special’ test-only objects you have, and working more closely with the objects your application uses. The disadvantage is that you can no longer add lots of test-only helper functions to describe how you’re playing with the clock more clearly.. at least, not in the ‘Clock’ object.

Can we change Rails to use a settable clock?

The Rails code refers to Time.now in many, many places (just try a global search of your gems directory, and you’ll see what I mean.) At this point, the hooks are not in convenient places to allow for a settable clock.. you would have to copy entire method signatures and tweak some Time.now references, which ties your changes to a particular Rails install and creates unfortunate forks in your code. So, I would say that the answer for now is, sadly, “No”.

Comments
  1. Interestingly enough, I’d just finished posting this
    article about controlling Time.now

    and turned to my RSS reader to find this article.

    I think that my technique goes further towards jacking up rails and replacing it’s sense of time, although there are still probably thread safety issues here.

  2. Nathan Wilmes says:

    Yeah, it looks like your approach would have many of the same issues that doing a global Time stub would have (i.e., Selenium would stop advancing time and benchmarking would get confused within your Time block). Since you’re limiting the area where you override time, benchmarking is a bit less of a problem, though.

  3. ed says:

    I needed something like this in my tests as well. I put the following in my test_helper.rb and it seems to work for me. I don’t need to change what class I access in my code – can still use Time.

    
    class Time
      class << self
        attr_accessor :warp
        alias_method :real_now, :now
        def now
          warp
        end
        alias_method :new, :now
      end
    end
    Time.warp = Time.real_now
    
    def pretend_now_is(time)
      begin
        Time.warp = time
        yield
      ensure
        Time.warp = Time.real_now
      end
    end
    

    Then I can just do:

    
    pretend_no_is(Time.now.end_of_month) do
      # time-specific code
    end
    
  4. Felix says:

    I use a custom function “current_time” to add an arbitrary offset to the current time. This is nice during development and when demoing the application. However, of course, there are places beyond my code where Time.now, etc. is used. It would be nice if one could specify an offset that affects all time objects/functions.

Post a Comment

Your Information (Name required. Email address will not be displayed with comment.)

* Copy This Password *

* Type Or Paste Password Here *