Ruby on rails Rails-测试使用DateTime.now的方法
我有一个使用DateTime.now的方法来对一些数据执行搜索,我想用不同的日期测试这个方法,但我不知道如何存根DateTime.now,我也不能让它与Timecop一起工作(如果它是这样工作的话) 随着时间的推移,我尝试了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.
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
。如果您在RSpecbefore
块中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。我已经更新了我的答案。