How to fake Time.now?

前端 未结 14 1064
不知归路
不知归路 2020-12-02 11:47

What\'s the best way to set Time.now for the purpose of testing time-sensitive methods in a unit test?

相关标签:
14条回答
  • 2020-12-02 12:19

    If you have ActiveSupport included, you could use:

    travel_to Time.zone.parse('2010-07-05 08:00')
    

    http://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html

    0 讨论(0)
  • 2020-12-02 12:19

    I allways extract Time.now into a separate method that I turn into attr_accessor in the mock.

    0 讨论(0)
  • 2020-12-02 12:20

    I really like the Timecop library. You can do time warps in block form (just like time-warp):

    Timecop.travel(6.days.ago) do
      @model = TimeSensitiveMode.new
    end
    assert @model.times_up!
    

    (Yes, you can nest block-form time travel.)

    You can also do declarative time travel:

    class MyTest < Test::Unit::TestCase
      def setup
        Timecop.travel(...)
      end
      def teardown
        Timecop.return
      end
    end
    

    I have some cucumber helpers for Timecop here. They let you do things like:

    Given it is currently January 24, 2008
    And I go to the new post page
    And I fill in "title" with "An old post"
    And I fill in "body" with "..."
    And I press "Submit"
    And we jump in our Delorean and return to the present
    When I go to the home page
    I should not see "An old post"
    
    0 讨论(0)
  • 2020-12-02 12:20

    Using Rspec 3.2, the only simple way I found to fake Time.now return value is :

    now = Time.parse("1969-07-20 20:17:40")
    allow(Time).to receive(:now) { now }
    

    Now Time.now will always return the date of Apollo 11 landing on the moon.

    Source: https://www.relishapp.com/rspec/rspec-mocks/docs

    0 讨论(0)
  • 2020-12-02 12:21

    Also see this question where I put this comment as well.

    Depending upon what you are comparing Time.now to, sometimes you can change your fixtures to accomplish the same goal or test the same feature. For example, I had a situation where I needed one thing to happen if some date was in the future and another to happen if it was in the past. What I was able to do was include in my fixtures some embedded ruby (erb):

    future:
        comparing_date: <%= Time.now + 10.years %>
        ...
    
    past:
        comparing_date: <%= Time.now - 10.years %>
        ...
    

    Then in your tests then you choose which one to use to test the different features or actions based upon the time relative to Time.now.

    0 讨论(0)
  • 2020-12-02 12:22

    This kind of works and allows for nesting:

    class Time
      class << self
        attr_accessor :stack, :depth
      end
    
      def self.warp(time)
    
        Time.stack ||= [] 
        Time.depth ||= -1 
        Time.depth += 1
        Time.stack.push time
    
        if Time.depth == 0 
          class << self    
              alias_method :real_now, :now  
              alias_method :real_new, :new
    
              define_method :now do
                stack[depth] 
              end
    
              define_method :new do 
                now 
              end
          end 
        end 
    
        yield
    
        Time.depth -= 1
        Time.stack.pop 
    
        class << self 
          if Time.depth < 0 
            alias_method :new, :real_new
            alias_method :now, :real_now
            remove_method :real_new
            remove_method :real_now 
          end
        end
    
      end
    end
    

    It could be slightly improved by undefing the stack and depth accessors at the end

    Usage:

    time1 = 2.days.ago
    time2 = 5.months.ago
    Time.warp(time1) do 
    
      Time.real_now.should_not == Time.now
    
      Time.now.should == time1 
      Time.warp(time2) do 
        Time.now.should == time2
      end 
      Time.now.should == time1
    end
    
    Time.now.should_not == time1 
    Time.now.should_not be_nil
    
    0 讨论(0)
提交回复
热议问题