Fork me on GitHub

Time warp for Rails testing

Time Warp

Update: This has been turned into a gem for plug-and-play reuse.

In my freelance time I find myself working with an internationalized site used in over 50 countries. Of course, users of the site wanted to deal with things in a localized time zone. Adding time zone awareness brings a special angle to testing and it saddled me with some errors I had not seen before.

To start, I made use of some excellent time warp code for the unit tests I had written up. It worked beautifully. That is, it worked beautifully until I ran rake or, more specifically, rake test:functionals.

My functional tests were dying hard with these new, handy additions to test_helper.rb. From the execution of the first test, I was seeing “stack trace too deep” errors.

./test/functional/../test_helper.rb:13:in real_now': stack level too deep (SystemStackError)
        from ./test/functional/../test_helper.rb:13:inreal_now'
        from ./test/functional/cms/../../test_helper.rb:13:in now'
        from /usr/local/lib/ruby/1.8/logger.rb:327:inadd'
        from /usr/local/lib/ruby/1.8/logger.rb:348:in debug'
        from /Users/bjhess/Sites/iridesco/afs/trunk/config/../vendor/rails/actionpack/lib/action_controller/helpers.rb:114:indefault_helper_module!'
        from /Users/bjhess/Sites/iridesco/afs/trunk/config/../vendor/rails/actionpack/lib/action_controller/helpers.rb:124:in send'
        from /Users/bjhess/Sites/iridesco/afs/trunk/config/../vendor/rails/actionpack/lib/action_controller/helpers.rb:124:ininherited'
        from /Users/bjhess/Sites/iridesco/afs/trunk/config/../app/controllers/cms/email_controller.rb:1
         ... 9 levels...
        from /Users/bjhess/Sites/iridesco/afs/trunk/config/../vendor/rails/activerecord/lib/../../activesupport/lib/active_support/dependencies.rb:489:in load'
        from /usr/local/lib/ruby/gems/1.8/gems/rake-0.7.3/lib/rake/rake_test_loader.rb:5
        from /usr/local/lib/ruby/gems/1.8/gems/rake-0.7.3/lib/rake/rake_test_loader.rb:5:ineach'
        from /usr/local/lib/ruby/gems/1.8/gems/rake-0.7.3/lib/rake/rake_test_loader.rb:5
rake aborted!
Command failed with status (1): [/usr/local/bin/ruby -Ilib:test "/usr/local...]

The specific bit of code appeared to be the real_now call on the line real_now - testing_offset:

alias_method :real_now, :now
def now
  real_now - testing_offset
end
alias_method :new, :now

My workaround was to move all of the time warp code into its own time_helper.rb file.

ENV["RAILS_ENV"] = "test"
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")

class Time #:nodoc:
  class <<self
    attr_accessor :testing_offset
    alias_method :real_now, :now
    def now
      real_now - testing_offset
    end
    alias_method :new, :now
  end
end
Time.testing_offset = 0

class Test::Unit::TestCase

  def pretend_now_is(time)
    begin
      Time.testing_offset = Time.now - time
      yield
    ensure
      Time.testing_offset = 0
    end
  end
end

Then I simply included the time_helper file along with test_helper in any test that required time warping:

require File.dirname(__FILE__) + ‘/../test_helper’
require File.dirname(__FILE__) + ‘/../time_helper’

class EventTest < Test::Unit::TestCase
  . . .
end

This worked beautifully. Unfortunately I’m at a loss as to why the initial setup had problems and I really wish I did know why. I don’t like being in the dark on problems.

Share if you like:
  • Digg
  • StumbleUpon
  • del.icio.us
  • Technorati
  • Ma.gnolia
  • Reddit
  • description
Related posts:

Comments (10)

  1. hugocf wrote:

    Congrats for the wonderful code! This time-based testing thing u wrote is great!

    Unfortunately, I too was at a loss with the stack-thingy, and at ~3am the brain isn’t properly now… u can’t imagine the joy of getting the answer straight from on the one-and-only hit google gave me!

    Love it when things work!!! :)

    (… now, back to work!)

    Thursday, August 23, 2007 at 8:45 pm #
  2. bjhess wrote:

    Thanks,

    I must admit, though, that I didn’t originate the code. I found the base code here at snippets.dzone.com. (That’s an excellent site, by the way.)

    My post was mainly to describe an error in using the code and my fix. I’m happy that helped you!

    Thursday, August 23, 2007 at 9:00 pm #
  3. hugocf wrote:

    Yes, it was the snipped that I originally found, but thought you were the creator.

    Nevertheless, your post was gold! ;)

    I’ve also found minor glitch on the original cod, which I posted back to the snippet page. You might find it useful to take a look.

    (ed: I fixed the link.)

    Thursday, August 23, 2007 at 10:22 pm #
  4. bjhess wrote:

    Thanks. And thanks for sharing your other glitch. Although I wonder if it would be more appropriate to set the Time offset to 0 at the beginning of that test?

    Friday, August 24, 2007 at 5:22 am #
  5. hugocf wrote:

    well, at least for me, “pretendnowis()” reads as something like absolute positioning the current time and not relative to the latest timeshift eventually made before…

    .. but anyways, there’s always more than one way of reading :)

    Friday, August 24, 2007 at 6:39 am #
  6. hugocf wrote:

    Mate, i think i might have found the “reason” for our +SystemStackError+ error. It reappeared using the `time_helper.rb` once I included it in _one-too-many_ test file.

    It has something to do with getting into a recursive declaration look, when the Time class is redefined over and over again.

    By making sure it only extends the Time class once, time error went away.

    See the “unless” clause wrapping the Time definition in the “time_helper.rb”

    
    unless Time.respond_to? :real_now # prevent the error: stack level too deep (SystemStackError)
      class Time #:nodoc:
        class <<self
          #... the same stuff as before
        end
      end
    end
    

    (ed: fixed some markup)

    Friday, August 24, 2007 at 9:19 am #
  7. hugocf wrote:

    (geee… :) I really can’t get your comments markup right!)

    Friday, August 24, 2007 at 9:20 am #
  8. bjhess wrote:

    That worked beautifully. Thanks, hugocf! Make sure to correct things out at dzone.

    As for formatting, I have Markdown running somewhere in here. I think that’s what got ya!

    Friday, August 24, 2007 at 4:46 pm #
  9. S. Potter wrote:

    It sounds like you are really just trying to “stub” Time.now. If I were you I would use mocha (if you are still using Test::Unit) or use the fully integrated BDD framework, rSpec.

    Both mocha and rspec are open source projects hosted on Rubyforge.

    To “stub” the now class method on Time in rSpec you would do:

    Time.stub!(:now).and_return(stored_time)

    In mocha (though it has been a while since I used it so not sure of exact API) you could do something like:

    Time.expects(:now).returns(stored_time)

    HTH

    Thursday, October 25, 2007 at 2:17 am #
  10. Barry wrote:

    Thanks for the advice!

    Thursday, October 25, 2007 at 9:04 pm #