Forum: Ruby Overriding Time.now

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Robert MannI (Guest)
on 2006-05-24 17:55
(Received via mailing list)
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
Robert D. (Guest)
on 2006-05-24 18:01
(Received via mailing list)
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
Robert MannI (Guest)
on 2006-05-24 18:39
(Received via mailing list)
Thanks alot Robert, that'll do it



Rob
Molitor, Stephen L (Guest)
on 2006-05-24 19:07
(Received via mailing list)
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
Robert D. (Guest)
on 2006-05-24 19:20
(Received via mailing list)
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
Molitor, Stephen L (Guest)
on 2006-05-24 20:15
(Received via mailing list)
> 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
Jim W. (Guest)
on 2006-05-24 21:01
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/classesarej...
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.
Robert D. (Guest)
on 2006-05-24 23:58
(Received via mailing list)
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
Logan C. (Guest)
on 2006-05-25 00:45
(Received via mailing list)
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
Robert D. (Guest)
on 2006-05-25 01:04
(Received via mailing list)
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
Molitor, Stephen L (Guest)
on 2006-05-25 01:22
(Received via mailing list)
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
Molitor, Stephen L (Guest)
on 2006-05-25 02:12
(Received via mailing list)
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
Pete Y. (Guest)
on 2006-05-30 04:15
(Received via mailing list)
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/
This topic is locked and can not be replied to.