无法与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)
因为Oin
build议be_within
匹配是最好的做法
…还有更多的东西 – > http://www.eq8.eu/blogs/27-rspec-be_within-matcher
但是如何处理这个问题的另外一个方法就是使用在midday
和middnight
属性中构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_time
testing帮助器方法:
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