views:

314

answers:

5

What is the best way to write unit test for code which gets current time? For example some object might be created only at business days, other objects take into account current time when checking permissions to execute some actions, etc.

I guess that I should mock up the Date.today and and Time.now. Is this right approach?

Update: Both solutions (a) Time.is and (b) Time.stubs(:now).returns(t) work. (a) is very nice approach but (b) solution will be more consistent with other test code.

At this question an author asks for a general solution. For Ruby, in my option two above solutions are simpler and therefore better than extracting code which gets current date / time.

BTW I recommend to use Chronic to get required time, e.g.

require 'Chronic'    
mon = Chronic.parse("next week monday")
Time.stubs(:now).returns(mon)
+3  A: 

Pass a ITimeProvider (for example) class to your routine to use to get the time, then you can mock it and use the mock object to always give you a consistent time for the routine to use.

Michael
+3  A: 

Right off the top of my head, I'd guess that the best approach to this would be to not let your object get the time itself. In other words, pass the date/time into whatever method is being called on the object that's using the built-in time constructs currently. This could, depending on your circumstances, be a much simpler solution than mocking up Date.today and Time.now, as you suggest.

Edit: I say this in sharp contrast to the suggestion of having an ITimeProvider interface that you pass in as a dependency...that's just overkill, in my opinion.

dustyburwell
A: 

Creating an ITimeProvider object as a dependancy is better than passing the time in because it fits the Don't Repeat Yourself principle.

Somewhere in your production code, something has to get the current time. You could take the date generation code outside the boundaries of your test coverage or you can have a single easily testable object that can be everywhere else.

somori
Typically, getting the current time is a single expression, e.g. new Date() in Java. Abstracting that into an interface is absurd overengineering. You'd probably end up repeating yourself even more than before.
Michael Borgwardt
It's certainly not for every project. I'd only consider doing something like this if I was heavily dependant on what the time was.
somori
Are you saying that there's a DRY violation because both the production code and the test code generate the time? If so, how does replacing it with a mock help? Also, is there a word missing in "or you can have a single easily testable object that can be everywhere else"?
Andrew Grimm
+4  A: 

The following is from Jay Field's Thoughts. It allows you to redefine Time.now for the duration of a block.

require 'time'

class Time
  def self.metaclass
    class << self; self; end
  end

  def self.is(point_in_time)
    new_time = case point_in_time
      when String then Time.parse(point_in_time)
      when Time then point_in_time
      else raise ArgumentError.new("argument should be a string or time instance")
    end
    class << self
      alias old_now now
    end
    metaclass.class_eval do
      define_method :now do
        new_time
      end
    end
    yield
    class << self
      alias now old_now
      undef old_now
    end
  end
end

Time.is(Time.now) do
  Time.now # => Tue Nov 13 19:31:46 -0500 2007
  sleep 2
  Time.now # => Tue Nov 13 19:31:46 -0500 2007
end

Time.is("10/05/2006") do
  Time.now # => Thu Oct 05 00:00:00 -0400 2006
  sleep 2
  Time.now # => Thu Oct 05 00:00:00 -0400 2006
end
Aaron Hinni
+2  A: 

Mocking Time.now or Date.today seems straightforward enough, would look something like:

require 'rubygems'
require 'test/unit'
require 'mocha'

class MyClass

  def foo
    Time.now
  end

end

class MyTest < Test::Unit::TestCase

  def test_foo
    assert true
    t = Time.now
    Time.expects(:now).returns(t)
    assert_equal t, MyClass.new.foo
  end

end
dbarker