Overriding Time.now


#1

Hello!

I have a unit test where I need to simulate a different system time.

Hence I need Time.now to return a different time than now, say, now + 1
month

How can I do this?

I tried aliasing Time now, but I don’t know how to alias class methods.

class Time
alias_method ‘self.original_now’, ‘self.now’
end

How can I alias this --or-- is there any better way to simulate a
different
time on calling Time.now?

Tips and ideas are greatly appreciated,
Rob


#2

On 5/24/06, Robert MannI removed_email_address@domain.invalid wrote:

I tried aliasing Time now, but I don’t know how to alias class methods.
Tips and ideas are greatly appreciated,
Rob

Hi

class Time
class << self
alias_method :n, :now
end
end

hope that helps
Cheers
Robert


Deux choses sont infinies : l’univers et la bêtise humaine ; en ce qui
concerne l’univers, je n’en ai pas acquis la certitude absolue.

  • Albert Einstein

#3

Thanks alot Robert, that’ll do it

Rob


#4

One caution: I did what Robert suggested a while back. It works
perfectly, but does wreak havoc with Test::Unit and rake test times
reporting. Tests can appear to finish before they’ve started, etc. I
ended up creating a CurrentTime class:

class CurrentTime
@@now = nil

def now
@@now || Time.now
end

def now=(new_time)
@@now = new_time
end
end

I changed my code to use CurrentTime.now instead of Time.now.
Test::Unit, rake and friends still use Time.now, so they don’t get
messed up. In my unit test I mock out the time by calling
CurrentTime.now=. Crude but it works.

Steve


#5

On 5/24/06, Molitor, Stephen L removed_email_address@domain.invalid wrote:

@@now || Time.now

works.
Yup you have to change the code to be tested, not the best idea.
I guess you can avoid this by redefining now only in the class to be
tested,
please note I did not hang anybody (by not redefining Time.now) just
gave
you the rope to hang yourself (by redefining Time.now).
Now (pun intended) I believe that you can redefine Time.now in the scope
where you need it without interfering with Test::Unit, well you are
still
administring the code you test somehow.
Neverheless that might be the cleanest approach, I would be glad to have
a
better one suggested though.

Cheers
Robert

Steve

Hence I need Time.now to return a different time than now, say, now

class << self

Deux choses sont infinies : l’univers et la bêtise humaine ; en ce qui
concerne l’univers, je n’en ai pas acquis la certitude absolue.

  • Albert Einstein


Deux choses sont infinies : l’univers et la bêtise humaine ; en ce qui
concerne l’univers, je n’en ai pas acquis la certitude absolue.

  • Albert Einstein

#6

I believe that you can redefine Time.now in the scope where you need
it without interfering with Test::Unit

There’s only one instance of the Time class object, and any changes you
make to it are in effect ‘global’. At least in Ruby 1.8. I understand
there’s some talk of adding namespaces or something to be able to make
modifications and extensions to classes only apply in a certain scope in
Ruby 2.0.

Steve


#7

Molitor, Stephen L wrote:

I believe that you can redefine Time.now in the scope where you need
it without interfering with Test::Unit

There’s only one instance of the Time class object, and any changes you
make to it are in effect ‘global’.

You can use a technique called “Constant Injection” where you
temporarily change the definition of a constant within the scope of a
single class.

See http://onestepback.org/articles/depinj/classesarejustobjects.html
for an example.

This allows you to say something like:

def test_my_class
MyClass.use_class(:Time, MockTime) do
mc = MyClass.new
# Any calls to Time.now within MyClass will be routed to
MockTime.now
end
# Calls to Time.now are now back to normal.
end

– Jim W.


#8

On May 24, 2006, at 3:55 PM, Robert D. wrote:

Although I do not understand it (this is not an anomaly of the
universe
though), it does not look like it
please kindly look at this

You are declaring a new class named Time in your module. This may or
not work for the OP.

e.g.:

% cat overriding_time.rb
class Base
def self.something_invovling_time_now
Time.now
end
end

module Wrap
class Time
def self.now
“Whee”
end
end
end

class Child < Base
include Wrap
puts something_invovling_time_now
puts Time.now
end

% ruby overriding_time.rb
Wed May 24 16:42:11 EDT 2006
Whee


#9

On 5/24/06, Jim W. removed_email_address@domain.invalid wrote:

Molitor, Stephen L wrote:

I believe that you can redefine Time.now in the scope where you need
it without interfering with Test::Unit

There’s only one instance of the Time class object, and any changes you
make to it are in effect ‘global’.

Although I do not understand it (this is not an anomaly of the universe
though), it does not look like it
please kindly look at this

------------------------------------ >8

robert@roma:~/ruby/tests$ cat test1.rb;./test1.rb
#!/usr/bin/env ruby

require ‘date’

module ToBTested
class Time
class << self
def now; “right now?”; end
end
end

    puts "Inside module ToBTested: " << Time.now.to_s

end

puts "Outside module ToBTested: " << Time.now.to_s
Inside module ToBTested: right now?
Outside module ToBTested: Wed May 24 21:47:44 CEST 2006
--------------------------- 8<

*** HOWEVER ***
this theoretical possibility will probably not help the OP because
Test::Unit might not have access to the unaltered version of Time
(please
note, this is ruby-talk not physics-talk) when executing in Testcase
context.
I had no time (again) going through this during working hours but if
someone
would care to explain the exact location where the inconsistency occurs
withing Test::Unit I would gladly have a look.

Cheers
Robert


#10

Very nice! If I understand correctly, I could do something like this:

class Module
def inject_constant(constant, value)
old_value = const_get(constant)
const_set(constant, value)
if block_given?
yield
const_set(constant, old_value)
end
end
end

Then in a test, I could do this:

def test_my_app
MyApp::inject_constant(:Time, MockTime)
end

If all of my code is in the module MyApp, then all of my code uses
MockTime whenever it refers to Time. Forever in this case, which might
be fine in a test. Or I could pass a block to limit the duration. Or
use setup / teardown. If I want to be more fine grained I can just
inject into one class.

In any case, I can mock out the current time without messing up external
code, and without changing my original code. All without a fancy DI or
AOP framework. Wow!

Is this the cleanest implementation of this technique?

I went through all your DI presentation slides. Looks like it was a
fantastic presentation.

Steve


#11

On 5/24/06, Logan C. removed_email_address@domain.invalid wrote:

On May 24, 2006, at 3:55 PM, Robert D. wrote:

[snip]
You are declaring a new class named Time in your module.

Of course, I completely screwed up! Sorry for the noise.

This may or

puts something_invovling_time_now
puts Time.now
end

% ruby overriding_time.rb
Wed May 24 16:42:11 EDT 2006
Whee


Deux choses sont infinies : l’univers et la bêtise humaine ; en ce qui
concerne l’univers, je n’en ai pas acquis la certitude absolue.

  • Albert Einstein

#12

Correction, I should probably wrap the user code in an ensure block to
ensure that the constant gets reset:

class Module
def inject_constant(constant, value)
old_value = const_get(constant)
const_set(constant, value)
if block_given?
begin
yield
ensure
const_set(constant, old_value)
end
end
end
end

Steve


#13

Following up a slightly old thread here, but I’ve taken a hybrid
approach to a couple already suggested and it seems to work well:

class Time
@@now = nil

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

 def self.forced_now
   @@now || unforced_now
 end

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

end

Then, in my test I can simply do Time.now = whatever. As long as I
put a Time.now = nil in my teardown, it doesn’t seem to mess up the
test timings. (Note that I’m doing this in Rails, so your mileage may
vary.) It’s important that the Time.now = nil goes in the teardown,
rather than in the test itself, to ensure that it runs even if a test
fails.

Cheers,

Pete Y.
http://9cays.com/