views:

1097

answers:

9

What's the best way to set Time.now for the purpose of testing time-sensitive methods in a Unit test? Thanks!

+6  A: 

Do the time-warp

time-warp is a library that does what you want. It gives you a method that takes a time and a block and anything that happens in the block uses the faked time.

pretend_now_is(2000,"jan",1,0) do
  Time.now
end
BaroqueBobcat
+8  A: 

Personally I prefer to make the clock injectable, like so:

def hello(clock=Time)
  puts "the time is now: #{clock.now}"
end

Or:

class MyClass
  attr_writer :clock

  def initialize
    @clock = Time
  end

  def hello
    puts "the time is now: #{@clock.now}"
  end
end

However, many prefer to use a mocking/stubbing library. In RSpec/flexmock you can use:

Time.stub!(:now).and_return(Time.mktime(1970,1,1))

Or in Mocha:

Time.stubs(:now).returns(Time.mktime(1970,1,1))
Avdi
The testing part is exactly the kind of answer I was going to provide.
railsninja
This is totally what I would have done as well. If you write your code in a way that makes it easy to test, you don't have to do arcane things in order to make it testable.
August Lilleaas
While I agree with the general idea of writing stuff in a more testable way, there are times when it is simply not practical. And Time.now is one of those examples. it can be used in a lot of parts of the system and passing it around all the time will be too much overhead.
Vitaly Kushner
+13  A: 

I really like the Timecop library. You can do time warps in block form (just like time-warp):

Timecop.travel(6.days.ago) do
  @model = TimeSensitiveMode.new
end
assert @model.times_up!

(Yes, you can nest block-form time travel.)

You can also do declarative time travel:

class MyTest < Test::Unit::TestCase
  def setup
    Timecop.travel(...)
  end
  def teardown
    Timecop.return
  end
end

I have some cucumber helpers for Timecop here. They let you do things like:

Given it is currently January 24, 2008
And I go to the new post page
And I fill in "title" with "An old post"
And I fill in "body" with "..."
And I press "Submit"
And we jump in our Delorean and return to the present
When I go to the home page
I should not see "An old post"
James A. Rosen
Should there be a quotation mark at the end of the last test?
Andrew Grimm
Yup. (Now I have to type some more for this comment to be long enough...)
James A. Rosen
A: 

This kind of works and allows for nesting:

class Time
  class << self
    attr_accessor :stack, :depth
  end

  def self.warp(time)

    Time.stack ||= [] 
    Time.depth ||= -1 
    Time.depth += 1
    Time.stack.push time

    if Time.depth == 0 
      class << self    
          alias_method :real_now, :now  
          alias_method :real_new, :new

          define_method :now do
            stack[depth] 
          end

          define_method :new do 
            now 
          end
      end 
    end 

    yield

    Time.depth -= 1
    Time.stack.pop 

    class << self 
      if Time.depth < 0 
        alias_method :new, :real_new
        alias_method :now, :real_now
        remove_method :real_new
        remove_method :real_now 
      end
    end

  end
end

It could be slightly improved by undefing the stack and depth accessors at the end

Usage:

time1 = 2.days.ago
time2 = 5.months.ago
Time.warp(time1) do 

  Time.real_now.should_not == Time.now

  Time.now.should == time1 
  Time.warp(time2) do 
    Time.now.should == time2
  end 
  Time.now.should == time1
end

Time.now.should_not == time1 
Time.now.should_not be_nil
Sam Saffron
A: 

Depending upon what you are comparing Time.now to, sometimes you can change your fixtures to accomplish the same goal or test the same feature. For example, I had a situation where I needed one thing to happen if some date was in the future and another to happen if it was in the past. What I was able to do was include in my fixtures some embedded ruby (erb):

future:
    comparing_date: <%= Time.now + 10.years %>
    ...

past:
    comparing_date: <%= Time.now - 10.years %>
    ...

Then in your tests then you choose which one to use to test the different features or actions based upon the time relative to Time.now.

Bryan Ward
A: 

Also see this question where I put this comment as well.

Depending upon what you are comparing Time.now to, sometimes you can change your fixtures to accomplish the same goal or test the same feature. For example, I had a situation where I needed one thing to happen if some date was in the future and another to happen if it was in the past. What I was able to do was include in my fixtures some embedded ruby (erb):

future:
    comparing_date: <%= Time.now + 10.years %>
    ...

past:
    comparing_date: <%= Time.now - 10.years %>
    ...

Then in your tests then you choose which one to use to test the different features or actions based upon the time relative to Time.now.

Bryan Ward
+2  A: 

I'm using RSpec and I did this: Time.stub!(:now).and_return(2.days.ago) before I call Time.now. In that way I'm able to control the time I used for that particular test case

Staelen
Wouldn't your tests eventually change though? Have any examples?
Trip
for e.g. if i have a rental system, i want to test if the system charge penalty fees if the day the item is returned is late, i'll need to manipulate Time.now so that when the item is returned, it'll be late... not sure if u get me though
Staelen
A: 

i just have this in my test file:

   def time_right_now
      current_time = Time.parse("07/09/10 14:20")
      current_time = convert_time_to_utc(current_date)
      return current_time
    end

and in my Time_helper.rb file i have a

  def time_right_now
    current_time= Time.new
    return current_time
  end

so when testing the time_right_now is overwritten to use what ever time you want it to be.

Mo