Rails与rspec的时间不一致

时间:2010-08-24 18:47:57

标签: ruby-on-rails time rspec

我正在使用Time in Rails并使用以下代码设置项目的开始日期和结束日期:

start_date ||= Time.now
end_date = start_date + goal_months.months

然后我克隆了对象,我正在编写rspec测试以确认副本中的属性是否匹配。 结束日期匹配:

original[end_date]:  2011-08-24 18:24:53 UTC
clone[end_date]:     2011-08-24 18:24:53 UTC

<击>

但是规范在开始日期给我一个错误:

expected: Wed Aug 24 18:24:53 UTC 2011,
     got: Wed, 24 Aug 2011 18:24:53 UTC +00:00 (using ==)

很明显,日期是相同的,只是格式不同。它们最终是如何在数据库中以不同方式存储的,以及如何使它们匹配?我已经尝试使用DateTime以及相同的结果。

更正:结束日期也不匹配。他们打印出相同的,但也有rspec错误。当我打印出开始日期和结束日期时,值以不同的格式出现:

start date: 2010-08-24T19:00:24+00:00
end date: 2011-08-24 19:00:24 UTC

10 个答案:

答案 0 :(得分:50)

这通常是因为rspec尝试匹配不同的对象:例如Time和DateTime。此外,可比较的时间可能有点不同,持续几毫秒。

在第二种情况下,正确的方法是使用存根和模拟。另请参阅TimeCop gem

在第一种情况下,可能的解决方案是比较时间戳:

actual_time.to_i.should == expected_time.to_i

我在这种情况下使用简单的matcher

# ./spec/spec_helper.rb
#
# Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each {|f| require f}
#
#
# Usage:
#
# its(:updated_at) { should be_the_same_time_as updated_at }
#
#
# Will pass or fail with message like:
#
# Failure/Error: its(:updated_at) { should be_the_same_time_as 2.days.ago }
# expected Tue, 07 Jun 2011 16:14:09 +0300 to be the same time as Mon, 06 Jun 2011 13:14:09 UTC +00:00


RSpec::Matchers.define :be_the_same_time_as do |expected|
  match do |actual|
    expected.to_i == actual.to_i
  end
end

答案 1 :(得分:23)

你应该模拟现在的Time方法,以确保它始终与规范中的日期相匹配。你永远不知道延迟何时会使规范因为几毫秒而失败。这种方法还将确保实际代码和规范上的时间相同。

如果您使用的是默认的rspec mock lib,请尝试执行以下操作:

t = Time.parse("01/01/2010 10:00")
Time.should_receive(:now).and_return(t)

答案 2 :(得分:20)

我完全同意以前关于存根Time.now的答案。也就是说这里还有另外一件事。当您比较数据库中的日期时间时,您会丢失ruby DateTime obj中可能存在的一些派系时间。在Rspec中以这种方式比较日期的最佳方法是:

database_start_date.should be_within(1).of(start_date)

答案 3 :(得分:6)

一个问题是Ruby Time对象具有纳秒级精度,但大多数数据库具有最高微秒精度。解决这个问题的最好方法是将Time.now(或使用timecop)存储为一个整数。阅读我写的关于此的帖子:http://blog.solanolabs.com/rails-time-comparisons-devil-details-etc/

答案 4 :(得分:0)

我最初的猜测是,Time.now的格式与数据库值的格式不同。

答案 5 :(得分:0)

您确定使用的是==而不是eqlbe吗?后两种方法使用对象标识而不是比较值。

从输出看起来预期值为Time,而正在测试的值为DateTime。这也可能是一个问题,但考虑到Ruby的日期和时间库的几乎病态性质,我会毫不犹豫地猜测如何修复它......

答案 6 :(得分:0)

我喜欢的一个解决方案是将以下内容添加到spec_helper

class Time
  def ==(time)
    self.to_i == time.to_i
  end
end

这样即使在嵌套对象中它也完全透明。

答案 7 :(得分:0)

.to_datetime添加到两个变量将强制日期时间值等效并尊重时区和夏令时。对于日期比较,请使用.to_date

包含两个变量的示例规范: actual_time.to_datetime.should == expected_time.to_datetime

清晰度更好的规范: actual_time.to_datetime.should eq 1.month.from_now.to_datetime

.to_i在规范中产生含糊不清的含义。

+1在规格中使用TimeCop gem。如果您的应用受DST影响,请确保在您的规格中测试夏令时。

答案 8 :(得分:0)

我们当前的解决方案是使用freeze_time方法来处理舍入:

def freeze_time(time = Time.zone.now)
  # round time to get rid of nanosecond discrepancies between ruby time and
  # postgres time
  time = time.round
  Timecop.freeze(time) { yield(time) }
end

然后您就可以使用它:

freeze_time do
  perform_work
  expect(message.reload.sent_date).to eq(Time.now)
end

答案 9 :(得分:0)

根据您的规范,您可以使用Rails-native travel helpers

# in spec_helper.rb
config.include ActiveSupport::Testing::TimeHelpers

start_date ||= Time.current.change(usecs: 0)
end_date = start_date + goal_months.months
travel_to start_date do
  # Clone here  
end
expect(clone.start_date).to eq(start_date)

如果没有Time.current.change(usecs: 0),可能会抱怨时区之间的差异。或者在微秒之间,因为帮助器将在内部重置传递的值(Timecop也有类似的问题,因此也可以重置usecs。)