Reading Rails - Time Travel

Rails comes with its very own time machine. In Rails 4.1 the travel and travel_to helpers were introduced. These provide a simple interface for testing time related code. For instance:

class CookieTest < ActiveSupport::TestCase
  test "cookies should be stale after 1 week" do
    cookie =

    travel 1.week do
      assert cookie.stale?

Let's see how Rails implements this bit of magic by reading some source code!

Where Do TimeHelpers Come From?

Our first order of business is to track down where these helpers are defined. If you look in ActiveSupport's testing/time_helpers.rb, and scroll down, you'll find the TimeHelpers module. This contains the helper methods, now let's find out how we're able to call them in tests.

Since TimeHelpers is a Module, something must include it. Searching ActiveSupport for TimeHelpers leads us to test_case.rb, and ActiveSupport::TestCase.

module ActiveSupport
  class TestCase < ::Minitest::Test

The default tests Rails generates inherit from Minitest::Test, so if you ever want to know more about how your tests work, you can read up on Minitest.

Scrolling down you'll see all the different modules ActiveSupport::TestCase includes:

include ActiveSupport::Testing::TaggedLogging
include ActiveSupport::Testing::SetupAndTeardown
include ActiveSupport::Testing::Assertions
include ActiveSupport::Testing::Deprecation
include ActiveSupport::Testing::TimeHelpers
extend ActiveSupport::Testing::Declarative

Sure enough, there's ActiveSupport::Testing::TimeHelpers. If you're ever curious about what Rails adds to Minitest, this is a great place to start looking.

Building a Ruby Time Machine

Jump back to testing/time_helpers.rb, and let's look at how travel is implemented:

def travel(duration, &block)
  travel_to + duration, &block

This just calls travel_to after adding duration to the current time. The ampersand on &block in the arguments means that if a block is passed in, it will be bound to the block variable. By putting the ampersand on &block when calling travel_to, Ruby will pass it as a block, not another argument.

Here's a quick illustration of how the & affects calls in Ruby:

def example(arg=nil, &block)
  puts "Got arg" if arg
  puts "Got block" if block

example{|x| x + 1}            # Got block
example({|x| x + 1})  # Got arg
example(&{|x| x + 1}) # Got block

Let's pick apart travel_to now.

def travel_to(date_or_time)
  if date_or_time.is_a?(Date) && !date_or_time.is_a?(DateTime)
    now = date_or_time.midnight.to_time
    now = date_or_time.to_time.change(usec: 0)

This method first determines what time we should should travel to. If it's a Date, and not a DateTime, ActiveSupport will assuming midnight of that day, which is probably reasonable. Otherwise it will be converted to a plain Time. I was curious about the change(usec: 0). This zeros out the microseconds, but why? Apparently according to the commit message, that is done to:

Fix rounding errors with #travel_to by resetting the usec on any passed time to zero, so we only travel with per-second precision, not anything deeper than that. DHH

When in doubt, git annotate is always a great tool when you want to understand why code was written a certain way.

The next bit is where travel_to performs its magic:

simple_stubs.stub_object(Time, :now, now)
simple_stubs.stub_object(Date, :today, now.to_date)

If you haven't used libraries like mocha, you may not be familiar with stubs. Stubbing an object means you're going to bypass its normal functionality for testing purposes. simple_stubs.stub_object is redefining Time and Date to always return the value now that we got above. We'll dig into SimpleStubs after we finish this method.

travel_to finishes with some cleanup code:

if block_given?

If a block is passed in this will call then block, and then call travel_back, which undoes the stubbing above. The code in ensure will always get called, even if an exception gets raised. If you ever need to be certain that some cleanup code is called, consider using begin and ensure.

What happens if a block isn't passed in? In that case, Time and Date will remain stubbed until you call travel_back. If you're not careful, you may end up affecting other tests. To avoid this issue, consider calling travel_back after each test.

def teardown
  # Reset Time/Date

Now what does simple_stubs do?

def simple_stubs
  @simple_stubs ||=

Looks like were' going to have to dig a little deeper.

Rails' Secret Stubbing Library

Rails doesn't advertise SimpleStubs, so you probably shouldn't be calling it directly. In fact it's explicitly not documented, but that doesn't mean we shouldn't figure out how it works.

So what's in this little class? It starts off by declaring a simple class using Struct:

module ActiveSupport
  module Testing
    class SimpleStubs # :nodoc:
      Stub =, :method_name, :original_method)

Using Struct is a great alternative to using hashes. It gives you a simple class with accessors for symbol passed in.

SimpleStubs#initialize creates a Hash to track the stubbed methods:

def initialize
  @stubs = {}

Now for the real trick, temporarily redefining a method on a single object:

def stub_object(object, method_name, return_value)
  key = [object.object_id, method_name]

  if stub = @stubs[key]

  new_name = "__simple_stub__#{method_name}"

  @stubs[key] =, method_name, new_name)

  object.singleton_class.send :alias_method, new_name, method_name
  object.define_singleton_method(method_name) { return_value }

Let's walk through what happens when travel_to calls this method with simple_stubs.stub_object(Time, :now, now).

First a key is generated so the stub can be looked up later. stub_object only changes a single object, so key is composed of that object's unique object_id and the name of the method we're stubbing. So for this might look like:

key = [Time.object_id, :now] #=> [19463520, :now]

Next, stub_object checks to see if the method is already stubbed, if so, it undoes the previous stubbing.

A temporary name is used to keep a reference to the old method so Rails can swap it back into place in unstub_object.

new_name = "__simple_stub__#{method_name}" #=> "__simple_stub__now"

Finally, the original method is aliased to a new name, and a new method is defined in it's place:

object.singleton_class.send :alias_method, new_name, method_name
object.define_singleton_method(method_name) { return_value }

For our example, that means that will now be called Time.__simple_stub__now, and a new method will be defined that just returns now that was passed in. For our purposes, singleton_class returns a class that can be modified for just this object, and define_singleton_method defines a method only on this object.


We learned how Rails' TimeHelpers work. Along the way, we also saw a simple example of how methods can be stubbed out in tests. Here are a few other tidbits to take away from this code:

If you want to go further, you might consider reading some of the other modules that ActiveSupport includes like ActiveSupport::Testing::Declarative.

blog comments powered by Disqus
Monkey Small Crow Small