Ruby on rails Rails-测试使用DateTime.now的方法

Ruby on rails Rails-测试使用DateTime.now的方法,ruby-on-rails,datetime,ruby-on-rails-4,rspec,timecop,Ruby On Rails,Datetime,Ruby On Rails 4,Rspec,Timecop,我有一个使用DateTime.now的方法来对一些数据执行搜索,我想用不同的日期测试这个方法,但我不知道如何存根DateTime.now,我也不能让它与Timecop一起工作(如果它是这样工作的话) 随着时间的推移,我尝试了 it 'has the correct amount if falls in the previous month' do t = "25 May".to_datetime Timecop.travel(t) puts DateTime.

我有一个使用DateTime.now的方法来对一些数据执行搜索,我想用不同的日期测试这个方法,但我不知道如何存根DateTime.now,我也不能让它与Timecop一起工作(如果它是这样工作的话)

随着时间的推移,我尝试了

it 'has the correct amount if falls in the previous month' do
      t = "25 May".to_datetime
      Timecop.travel(t)
      puts DateTime.now

      expect(@employee.monthly_sales).to eq 150
end 
当我运行规范时,我可以看到puts DateTime.now给出了
2015-05-25T01:00:00+01:00
,但具有相同的puts DateTime.now在方法中我正在测试输出
2015-07-24T08:57:53+01:00
(今天的日期)。 我怎样才能做到这一点

------------------更新---------------------------------------------------


我在(:all)之前的
块中设置记录(@employee等),这似乎是导致问题的原因。只有在
Timecop do
块之后完成设置时,它才起作用。为什么会出现这种情况?

Timecop应该能够处理您想要的。试着在运行测试之前冻结时间,而不是仅仅旅行,然后在完成测试后解冻。像这样:

before do
  t = "25 May".to_datetime
  Timecop.freeze(t)
end

after do
  Timecop.return
end

it 'has the correct amount if falls in the previous month' do
  puts DateTime.now
  expect(@employee.monthly_sales).to eq 150
end 
来自Timecop自述:

冻结用于静态模拟now的概念。当您的程序执行时,Time.now将不会更改,除非您对Timecop API进行后续调用。另一方面,旅行计算我们当前认为的Time.now is(回想一下,我们支持嵌套旅行)和传入的时间之间的偏移量。它使用此偏移来模拟时间的流逝

所以你想把时间冻结在某个地方,而不是仅仅旅行到那个时间。因为时间会像平常一样随着旅行而流逝,但是从一个不同的起点开始

如果这仍然不起作用,您可以将方法调用放入带有Timecop的块中,以确保它冻结块内的时间,如:

t = "25 May".to_datetime
Timecop.travel(t) do # Or use freeze here, depending on what you need
  puts DateTime.now
  expect(@employee.monthly_sales).to eq 150
end

TL;DR:问题是在规范中调用
Timecop.freeze
之前,在
Employee
中调用了
DateTime.now

Timecop模拟
Time
Date
DateTime
的构造函数。在
冻结
返回
(或
冻结
块内)之间创建的任何实例都将被模拟。
freeze
之前或
return
之后创建的任何实例都不会受到影响,因为Timecop不会干扰现有对象

从(我的重点):

gem提供了“时间旅行”和“时间冻结”功能,使得测试依赖时间的代码非常简单它提供了一个统一的方法来模拟单个调用中的Time.now、Date.today和DateTime.now。

因此,在创建要模拟的
Time
对象之前,必须调用
Timecop.freeze
。如果您在RSpec
before
块中
freeze
,这将在评估
subject
之前运行。但是,如果您有一个
before
块用于设置主题(
@employee
,在您的情况下),并且在嵌套的
descripe
中有另一个
before
块,那么您的主题已经设置好,在冻结时间之前调用了
DateTime.new


如果将以下内容添加到
员工

class Employee
  def now
    DateTime.now
  end
end
然后运行以下规范:

describe '#now' do
  let(:employee) { @employee }
  it 'has the correct amount if falls in the previous month', focus: true do
    t = "25 May".to_datetime
    Timecop.freeze(t) do
      expect(DateTime.now).to eq t
      expect(employee.now).to eq t

      expect(employee.now.class).to be DateTime
      expect(employee.now.class.object_id).to be DateTime.object_id
    end
  end
end
除了使用
freeze
块,您还可以在rspec
中的
freeze
return
钩子:

describe Employee do
  let(:frozen_time) { "25 May".to_datetime }
  before { Timecop.freeze(frozen_time) }
  after { Timecop.return }
  subject { FactoryGirl.create :employee }

  it 'has the correct amount if falls in the previous month' do
    # spec here
  end

end

离题了,但也许可以看看

我在Timecop和其他神奇的东西上遇到了一些问题,这些东西会弄乱Date、Time和DateTime类及其方法。我发现只使用依赖注入更好:

员工代码

class Employee
  def monthly_sales(for_date = nil)
    for_date ||= DateTime.now

    # now calculate sales for 'for_date', instead of current month
  end
end 
规格

it 'has the correct amount if falls in the previous month' do
  t = "25 May".to_datetime
  expect(@employee.monthly_sales(t)).to eq 150
end 

我们,Ruby世界的人们,从使用一些魔术中找到了极大的乐趣,而那些使用表达能力较差的编程语言的人是无法利用这些魔术的。但这种情况下,魔法太黑暗,应该真正避免。只需使用普遍接受的依赖注入最佳实践方法即可。

您的代码应该可以工作(完成后请记住调用
Timecop.return
)。你能粘贴
月度销售
方法的正文吗?该方法除了调用DateTime之外是不相关的。现在,恐怕我帮不了你。@raphael\u turtle你有机会看看我的答案吗?如果
expect(employee.now)的话,这两个方法仍然失败。要使t
通过,那么Timecop工作正常。您是否在
Timecop.freeze
块内尝试过您的规范?它在冻结块内工作,但仅当数据在它内设置时才起作用。在这种情况下,您不是在
每月销售
中创建
日期时间
,而是在
初始化
?如果是这样,请尝试在hoos之前和之后的
中设置Timecop。我已经更新了我的答案。