Greg Dan
Greg Dan

Reputation: 6318

Unit testing code which gets current time

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)

Upvotes: 5

Views: 1895

Answers (5)

dstnbrkr
dstnbrkr

Reputation: 4335

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

Upvotes: 3

Aaron Hinni
Aaron Hinni

Reputation: 14716

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

Upvotes: 6

Simon Gill
Simon Gill

Reputation: 1106

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.

Upvotes: 0

dustyburwell
dustyburwell

Reputation: 5813

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.

Upvotes: 3

Michael
Michael

Reputation: 518

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.

Upvotes: 3

Related Questions