我正在使用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 ==)
答案 0 :(得分:175)
我发现使用be_within
默认的rspec匹配器更优雅:
expect(@article.updated_at.utc).to be_within(1.second).of Time.now
答案 1 :(得分:134)
Ruby Time对象保持比数据库更高的精度。当从数据库中读回值时,它仅保留为微秒精度,而内存中表示精确到纳秒。
如果你不关心毫秒差异,你可以在期望的两边做一个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)
有关时间差异的更多信息,请参阅this
答案 2 :(得分:8)
旧帖子,但我希望它可以帮助任何进入这里寻求解决方案的人。我认为手动创建日期更简单,更可靠:
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
这可以确保存储的日期是正确的,不会执行to_x
或担心小数。
答案 3 :(得分:8)
是Oin
建议be_within
匹配是最好的做法
...它还有一些更多的用户 - > http://www.eq8.eu/blogs/27-rspec-be_within-matcher
但是,如何处理这个问题的另一种方法是使用内置midday
和middnight
属性的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
现在这只是为了演示!
我不会在控制器中使用它,因为你所有的Time.new calls =>所有时间属性将具有相同的时间=>可能无法证明您正试图实现的概念。我通常在组合的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,我也建议坚持使用be_within
匹配器!
所以是的,请坚持使用be_within
matcher;)
更新完成情况
评论中的问题:
如果时间是在哈希中怎么办?任何使expect(hash_1).to eq(hash_2)的方法工作时,一些hash_1值是pre-db-times,而hash_2中的相应值是post-db-times? -
expect({mytime: Time.now}).to match({mytime: be_within(3.seconds).of(Time.now)}) `
您可以将任何RSpec匹配器传递给match
匹配器
(例如,您甚至可以API testing with pure RSpec)
对于“post-db-times”,我猜你的意思是指保存到DB后生成的字符串。我建议将这种情况解耦为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)
但是如果在你的测试套件中经常出现这种情况,我建议你写own RSpec matcher(例如be_near_time_now_db_string
)将db字符串时间转换为Time对象,然后将其用作{{{}的一部分。 1}}:
match(hash)
答案 4 :(得分:7)
您可以将日期/日期时间/时间对象转换为字符串,因为它使用to_s(:db)
存储在数据库中。
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)
答案 5 :(得分:6)
我发现这个问题的最简单方法是创建一个current_time
测试助手方法,如下所示:
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
答案 6 :(得分:0)
因为我正在比较哈希,所以大多数解决方案都不适合我,所以我发现最简单的解决方案是简单地从要比较的哈希中获取数据。由于update_at时间实际上对我来说没有用,因此无法正常工作。
data = { updated_at: Date.new(2019, 1, 1,), some_other_keys: ...}
expect(data).to eq(
{updated_at: data[:updated_at], some_other_keys: ...}
)