Stubbing Time.now with Mocha

Mocha seems a great way to stub Time.now for testing expiration dates, etc.
with rspec. I can get it to stub a single time easily:

  Time.stubs(:now).returns(Time.at(1))

but what I'd really like to do is do that multiple times during a test:

  specify "with an expired cookie should return nil"
    Time.stubs(:now).returns(Time.parse("Jan 1 2001"))
    louie.login
    cookie = louie.remember_me

    Time.stubs(:now).returns(Time.parse("Oct 1 2001"))
    louie.login_with_cookie(cookie).should_be_nil
  end

However, only the first stubs call has any effect. I've tried stubbing it
to a FakeTime object:

  Time.stubs(:now).returns(FakeTime.now)

but it only looks at the value of FakeTime.now when the stub is created.
Is there a way using Mocha/Stubba to stub a routine and have it call
another for the return value? I've tried creating procs/lambdas, but I
must be doing something wrong - I get an infinite recursion.

Jay Levitt

Jay,

I don't know about Mocha, but you have to be a bit careful about replacing Time.now because the test timings depend on it. The way I do it is as follows. First, I've got a little plugin that includes this:

   class Time
     @@now = nil

     def self.now=(time)
       @@now = time
     end

     def self.forced_now #:nodoc:
       @@now || unforced_now
     end

     class << self
       alias_method :unforced_now, :now
       alias_method :now, :forced_now
     end
   end

and then in my test helper:

   def at_time(time)
     time = Time.parse(time + ' UTC') unless time.instance_of?(Time)
     Time.now = time
     begin
       yield
     ensure
       Time.now = nil
     end
   end

That lets me do something like this in a test:

   def test_whatever
     at_time("Jan 1 2001") do
       # Tests go in here.
     end
   end

Hope that helps!

Cheers,

Pete Yandell

···

On 06/09/2006, at 7:25 AM, Jay Levitt wrote:

Mocha seems a great way to stub Time.now for testing expiration dates, etc.
with rspec. I can get it to stub a single time easily:

  Time.stubs(:now).returns(Time.at(1))

but what I'd really like to do is do that multiple times during a test:

  specify "with an expired cookie should return nil"
    Time.stubs(:now).returns(Time.parse("Jan 1 2001"))
    louie.login
    cookie = louie.remember_me

    Time.stubs(:now).returns(Time.parse("Oct 1 2001"))
    louie.login_with_cookie(cookie).should_be_nil
  end

However, only the first stubs call has any effect. I've tried stubbing it
to a FakeTime object:

  Time.stubs(:now).returns(FakeTime.now)

but it only looks at the value of FakeTime.now when the stub is created.
Is there a way using Mocha/Stubba to stub a routine and have it call
another for the return value? I've tried creating procs/lambdas, but I
must be doing something wrong - I get an infinite recursion.

Jay Levitt

Oops...not paying attention to which list I was reading, so my answer was Rails-specific! The code still applies though, and still has the advantage of restoring time to normality once you're done.

Pete Yandell

···

On 06/09/2006, at 9:21 AM, Pete Yandell wrote:

Jay,

I don't know about Mocha, but you have to be a bit careful about replacing Time.now because the test timings depend on it. The way I do it is as follows. First, I've got a little plugin that includes this:

  class Time
    @@now = nil

    def self.now=(time)
      @@now = time
    end

    def self.forced_now #:nodoc:
      @@now || unforced_now
    end

    class << self
      alias_method :unforced_now, :now
      alias_method :now, :forced_now
    end
  end

and then in my test helper:

  def at_time(time)
    time = Time.parse(time + ' UTC') unless time.instance_of?(Time)
    Time.now = time
    begin
      yield
    ensure
      Time.now = nil
    end
  end

That lets me do something like this in a test:

  def test_whatever
    at_time("Jan 1 2001") do
      # Tests go in here.
    end
  end

Hope that helps!

Cheers,

Pete Yandell

On 06/09/2006, at 7:25 AM, Jay Levitt wrote:

Mocha seems a great way to stub Time.now for testing expiration dates, etc.
with rspec. I can get it to stub a single time easily:

  Time.stubs(:now).returns(Time.at(1))

but what I'd really like to do is do that multiple times during a test:

  specify "with an expired cookie should return nil"
    Time.stubs(:now).returns(Time.parse("Jan 1 2001"))
    louie.login
    cookie = louie.remember_me

    Time.stubs(:now).returns(Time.parse("Oct 1 2001"))
    louie.login_with_cookie(cookie).should_be_nil
  end

However, only the first stubs call has any effect. I've tried stubbing it
to a FakeTime object:

  Time.stubs(:now).returns(FakeTime.now)

but it only looks at the value of FakeTime.now when the stub is created.
Is there a way using Mocha/Stubba to stub a routine and have it call
another for the return value? I've tried creating procs/lambdas, but I
must be doing something wrong - I get an infinite recursion.

Jay Levitt

You can use Mocha to do what you want...

def test_me
  time_now = Time.parse("Jan 1 2001")
  Time.stubs(:now).returns( lambda { time_now } )
  assert_equal Time.parse("Jan 1 2001"), Time.now
  time_now = Time.parse("Oct 1 2001")
  assert_equal Time.parse("Oct 1 2001"), Time.now
end

The test times should be unaffected. The Time class should get put back to
normal in the test teardown.

For any other questions on Mocha, it's probably better to use the mailing
list - http://rubyforge.org/mailman/listinfo/mocha-developer

···

--
James.
http://blog.floehopper.org

On 06/09/06, Pete Yandell <pete@notahat.com> wrote:

Oops...not paying attention to which list I was reading, so my answer
was Rails-specific! The code still applies though, and still has the
advantage of restoring time to normality once you're done.

Pete Yandell

On 06/09/2006, at 9:21 AM, Pete Yandell wrote:

> Jay,
>
> I don't know about Mocha, but you have to be a bit careful about
> replacing Time.now because the test timings depend on it. The way I
> do it is as follows. First, I've got a little plugin that includes
> this:
>
> class Time
> @@now = nil
>
> def self.now=(time)
> @@now = time
> end
>
> def self.forced_now #:nodoc:
> @@now || unforced_now
> end
>
> class << self
> alias_method :unforced_now, :now
> alias_method :now, :forced_now
> end
> end
>
> and then in my test helper:
>
> def at_time(time)
> time = Time.parse(time + ' UTC') unless time.instance_of?(Time)
> Time.now = time
> begin
> yield
> ensure
> Time.now = nil
> end
> end
>
> That lets me do something like this in a test:
>
> def test_whatever
> at_time("Jan 1 2001") do
> # Tests go in here.
> end
> end
>
> Hope that helps!
>
> Cheers,
>
> Pete Yandell
>
> On 06/09/2006, at 7:25 AM, Jay Levitt wrote:
>
>> Mocha seems a great way to stub Time.now for testing expiration
>> dates, etc.
>> with rspec. I can get it to stub a single time easily:
>>
>> Time.stubs(:now).returns(Time.at(1))
>>
>> but what I'd really like to do is do that multiple times during a
>> test:
>>
>> specify "with an expired cookie should return nil"
>> Time.stubs(:now).returns(Time.parse("Jan 1 2001"))
>> louie.login
>> cookie = louie.remember_me
>>
>> Time.stubs(:now).returns(Time.parse("Oct 1 2001"))
>> louie.login_with_cookie(cookie).should_be_nil
>> end
>>
>> However, only the first stubs call has any effect. I've tried
>> stubbing it
>> to a FakeTime object:
>>
>> Time.stubs(:now).returns(FakeTime.now)
>>
>> but it only looks at the value of FakeTime.now when the stub is
>> created.
>> Is there a way using Mocha/Stubba to stub a routine and have it call
>> another for the return value? I've tried creating procs/lambdas,
>> but I
>> must be doing something wrong - I get an infinite recursion.
>>
>> Jay Levitt
>>
>