无法与RSpec比较时间

我正在使用Ruby on Rails 4和rspec-rails gem 2.14。 对于我的对象,我想比较当前时间与控制器操作运行后updated_at对象属性,但我有麻烦,因为规范没有通过。 也就是说,鉴于以下是规范代码:

 it "updates updated_at attribute" do Timecop.freeze patch :update @article.reload expect(@article.updated_at).to eq(Time.now) end 

当我运行上面的规范,我得到以下错误:

 Failure/Error: expect(@article.updated_at).to eq(Time.now) expected: 2013-12-05 14:42:20 UTC got: Thu, 05 Dec 2013 08:42:20 CST -06:00 (compared using ==) 

我怎样才能使规范通过?


注意 :我也尝试了以下(注意utc另外):

 it "updates updated_at attribute" do Timecop.freeze patch :update @article.reload expect(@article.updated_at.utc).to eq(Time.now) end 

但规格仍然没有通过(注意“有”价值差异):

 Failure/Error: expect(@article.updated_at.utc).to eq(Time.now) expected: 2013-12-05 14:42:20 UTC got: 2013-12-05 14:42:20 UTC (compared using ==) 

Ruby时间对象维护比数据库更高的精度。 从数据库中读取值时,只能保留为微秒的精度,而内存中的表示精确到纳秒。

如果你不关心毫秒的差异,你可以在期望的两边做一个to_s / to_i

 expect(@article.updated_at.utc.to_s).to eq(Time.now.to_s) 

要么

 expect(@article.updated_at.utc.to_i).to eq(Time.now.to_i) 

有关更多信息,请参阅此处了解时间有何不同

我发现使用be_within默认rspec匹配器更优雅:

 expect(@article.updated_at.utc).to be_within(1.second).of Time.now 

旧post,但我希望它能帮助任何人进入这里寻求解决办法。 我认为手动创builddate更简单也更可靠:

 it "updates updated_at attribute" do freezed_time = Time.utc(2015, 1, 1, 12, 0, 0) #Put here any time you want Timecop.freeze(freezed_time) patch :update @article.reload expect(@article.updated_at).to eq(freezed_time) end 

这确保了存储的date是正确的,没有做to_x或担心小数。

您可以使用to_s(:db)将date/date时间/时间对象转换为string,因为它存储在数据库中。

 expect(@article.updated_at.to_s(:db)).to eq '2015-01-01 00:00:00' expect(@article.updated_at.to_s(:db)).to eq Time.current.to_s(:db) 

因为Oinbuild议be_within匹配是最好的做法

…还有更多的东西 – > http://www.eq8.eu/blogs/27-rspec-be_within-matcher

但是如何处理这个问题的另外一个方法就是使用在middaymiddnight属性中构build的Rails。

 it do # ... stubtime = Time.now.midday expect(Time).to receive(:now).and_return(stubtime) patch :update expect(@article.reload.updated_at).to eq(stubtime) # ... end 

现在这只是示范!

我不会在控制器中使用这个function,因为您正在对所有Time.new调用进行存根=>所有时间属性将具有相同的时间=>可能无法certificate您正试图实现的概念。 我通常在组合的Ruby对象中使用它类似于这样的:

 class MyService attr_reader :time_evaluator, resource def initialize(resource:, time_evaluator: ->{Time.now}) @time_evaluator = time_evaluator @resource = resource end def call # do some complex logic resource.published_at = time_evaluator.call end end require 'rspec' require 'active_support/time' require 'ostruct' RSpec.describe MyService do let(:service) { described_class.new(resource: resource, time_evaluator: -> { Time.now.midday } ) } let(:resource) { OpenStruct.new } it do service.call expect(resource.published_at).to eq(Time.now.midday) end end 

但是,诚实地说,即使比较Time.now.midday,我也build议坚持使用be_within匹配器。

所以是的,坚持be_within匹配器;)


更新2017-02

问题在评论:

如果时间在哈希中呢? 任何方式使期望(hash_1).to eq(hash_2)工作时,一些hash_1值pre-db时间和hash_2中相应的值后db时间? –

 expect({mytime: Time.now}).to match({mytime: be_within(3.seconds).of(Time.now)}) ` 

您可以将任何RSpec匹配器传递给match器(比如,您甚至可以使用纯RSpec进行APItesting )

至于“后分贝时间”,我猜你是指保存到数据库后生成的string。 我会build议解耦这种情况下2期望(一个确保哈希结构,第二次检查时间)所以你可以做这样的事情:

 hash = {mytime: Time.now.to_s(:db)} expect(hash).to match({mytime: be_kind_of(String)) expect(Time.parse(hash.fetch(:mytime))).to be_within(3.seconds).of(Time.now) 

但是如果这种情况在您的testing套件中经常出现,我会build议编写您自己的RSpec匹配器 (例如be_near_time_now_db_string ),将dbstring时间转换为Time对象,然后将其用作match(hash)

  expect(hash).to match({mytime: be_near_time_now_db_string}) # you need to write your own matcher for this to work. 

我发现这个问题最简单的方法是创build一个像这样的current_timetesting帮助器方法:

 module SpecHelpers # Database time rounds to the nearest millisecond, so for comparison its # easiest to use this method instead def current_time Time.zone.now.change(usec: 0) end end RSpec.configure do |config| config.include SpecHelpers end 

现在时间总是四舍五入到最接近的毫秒来比较是直截了当的:

 it "updates updated_at attribute" do Timecop.freeze(current_time) patch :update @article.reload expect(@article.updated_at).to eq(current_time) end