如何假冒Time.now?

时间:2009-07-31 22:46:21

标签: ruby unit-testing time

为了在单元测试中测试时间敏感方法,设置Time.now的最佳方法是什么?

15 个答案:

答案 0 :(得分:74)

我非常喜欢Timecop库。您可以以块形式执行时间扭曲(就像时间扭曲一样):

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

(是的,你可以嵌套块状时间旅行。)

你也可以做声明时间旅行:

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

我为Timecop cucumber提供了一些here帮助器。他们让你做的事情如下:

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"

答案 1 :(得分:42)

我个人更喜欢让时钟可注射,如下:

def hello(clock=Time)
  puts "the time is now: #{clock.now}"
end

或者:

class MyClass
  attr_writer :clock

  def initialize
    @clock = Time
  end

  def hello
    puts "the time is now: #{@clock.now}"
  end
end

但是,许多人更喜欢使用模拟/存根库。在RSpec / flexmock中,您可以使用:

Time.stub!(:now).and_return(Time.mktime(1970,1,1))

或者在摩卡:

Time.stubs(:now).returns(Time.mktime(1970,1,1))

答案 2 :(得分:14)

我正在使用RSpec,我在调用Time.now之前执行了此操作: Time.stub!(:now).and_return(2.days.ago)。通过这种方式,我能够控制我用于特定测试用例的时间

答案 3 :(得分:10)

执行time-warp

time-warp是一个可以满足您需求的库。它为您提供了一个花费时间和块的方法,并且块中发生的任何事情都使用伪造的时间。

pretend_now_is(2000,"jan",1,0) do
  Time.now
end

答案 4 :(得分:9)

使用Rspec 3.2,我发现伪造Time.now返回值的唯一简单方法是:

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

现在Time.now将永远返回阿波罗11登陆月球的日期。

来源:https://www.relishapp.com/rspec/rspec-mocks/docs

答案 5 :(得分:7)

不要忘记Time只是一个引用类对象的常量。如果您愿意发出警告,您可以随时

real_time_class = Time
Time = FakeTimeClass
# run test
Time = real_time_class

答案 6 :(得分:1)

另请参阅this question我在哪里发表此评论。

根据您对Time.now的比较,有时您可以更改灯具以实现相同的目标或测试相同的功能。例如,我有一种情况,即如果某个日期在未来,我需要发生一件事情,如果是在过去,则会发生另一件事。我能做的是在我的灯具中加入一些嵌入式红宝石(erb):

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

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

然后在测试中,您可以根据相对于Time.now的时间选择使用哪一个来测试不同的功能或操作。

答案 7 :(得分:1)

如果出现同样的问题,我不得不假装某个特定日期和时间的规格时间:

Time.stub!(:now).and_return(Time.mktime(2014,10,22,5,35,28))        

这会给你:

2014-10-22 05:35:28 -0700

答案 8 :(得分:1)

如果您包含ActiveSupport,则可以使用:

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

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

答案 9 :(得分:0)

这种作品允许嵌套:

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

可以通过在末尾取消堆栈和深度访问器来稍微改进

用法:

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

答案 10 :(得分:0)

根据您对Time.now的比较,有时您可以更改灯具以实现相同的目标或测试相同的功能。例如,我有一种情况,即如果某个日期在未来,我需要发生一件事情,如果是在过去,则会发生另一件事。我能做的是在我的灯具中加入一些嵌入式红宝石(erb):

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

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

然后在测试中,您可以根据相对于Time.now的时间选择使用哪一个来测试不同的功能或操作。

答案 11 :(得分:0)

我只是在我的测试文件中有这个:

   def time_right_now
      current_time = Time.parse("07/09/10 14:20")
      current_time = convert_time_to_utc(current_date)
      return current_time
    end

在我的Time_helper.rb文件中我有一个

  def time_right_now
    current_time= Time.new
    return current_time
  end

所以在测试时,time_right_now会被覆盖,以便随时使用它。

答案 12 :(得分:0)

我总是将Time.now提取到一个单独的方法中,然后在模拟中转换为attr_accessor

答案 13 :(得分:0)

最近发布的Test::Redef使这个和其他伪造变得容易,即使没有以依赖注入样式重构代码(如果您使用其他人的代码,这将特别有用)。 )

fake_time = Time.at(12345) # ~3:30pm UTC Jan 1 1970
Test::Redef.rd 'Time.now' => proc { fake_time } do
   assert_equal 12345, Time.now.to_i
end

但是,要小心其他方法来获取时间,这不会伪造(Date.new,一个编译的扩展,它自己进行系统调用,接口到外部数据库服务器,知道当前的时间戳等。听起来上面的Timecop库可能会克服这些限制。

其他很棒的用途包括测试“当我尝试使用这个友好的http客户端时会发生什么,但它决定将此异常提高而不是返回一个字符串?”没有实际设置导致该异常的网络条件(这可能是棘手的)。它还允许您检查重新定义函数的参数。

答案 14 :(得分:0)

我自己的解决方案 https://github.com/igorkasyanchuk/rails_time_travel - 带有 UI 的 gem,因此您无需在代码中硬编码任何日期时间。只需从用户界面更改即可。

它可能对您的 QA 团队或在登台中测试应用程序也非常有用。