Ruby on rails 无法将时间与RSpec进行比较
我使用的是RubyonRails4和RSpecRailsGem2.14。对于my对象,我希望在控制器操作运行后将当前时间与Ruby on rails 无法将时间与RSpec进行比较,ruby-on-rails,ruby,time,rspec,comparison,Ruby On Rails,Ruby,Time,Rspec,Comparison,我使用的是RubyonRails4和RSpecRailsGem2.14。对于my对象,我希望在控制器操作运行后将当前时间与updated_at对象属性进行比较,但我遇到了麻烦,因为规范没有通过。也就是说,给定以下规范代码: it "updates updated_at attribute" do Timecop.freeze patch :update @article.reload expect(@article.updated_at).to eq(Time.now) end
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
添加项):
但是规范仍然不通过(注意“got”值的差异):
Ruby Time对象比数据库保持更高的精度。当从数据库读回该值时,它仅保留到微秒精度,而内存中的表示精确到纳秒 如果你不在乎毫秒差,你可以在你期望的两边做一个to_s/to_i
expect(@article.updated_at.utc.to_s).to eq(Time.now.to_s)
或
有关时间不同原因的更多信息,请参阅我发现在默认rspec匹配器中使用
be_更优雅:
expect(@article.updated_at.utc).to be_within(1.second).of Time.now
这是一篇老文章,但我希望它能帮助任何来这里寻求解决方案的人。我认为手动创建日期更容易、更可靠:
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) do
patch :update
@article.reload
expect(@article.updated_at).to eq(freezed_time)
end
end
这样可以确保存储的日期是正确的,而无需执行to_x
或担心小数。您可以将日期/日期时间/时间对象转换为字符串,因为它存储在数据库中,使用to_s(:db)
是的,因为Oin
建议bein
matcher是最佳实践
…还有更多的USCASE->
但是处理这个问题的另一种方法是使用Rails内置的midday
和middnight
属性
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
现在这只是为了演示强>
我不会在控制器中使用它,因为您一直在存根。新调用=>所有时间属性将具有相同的时间=>可能无法证明您试图实现的概念。我通常在合成的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
但老实说,我建议即使在比较时间的时候,也要坚持使用bein
matcher
所以是的,请坚持使用be_in
matcher;)
更新2017-02
评论中的问题:
如果时间是散列的呢?当一些散列值是前db时间,而散列值2中的相应值是后db时间时,使expect(散列值1)和eq(散列值2)起作用的任何方法
您可以将任何RSpec匹配器传递给匹配器
匹配器
(例如,你甚至可以这样做)
至于“post db times”,我猜您指的是保存到db后生成的字符串。我建议将这种情况分解为两种预期(一种确保哈希结构,另一种检查时间),这样您就可以执行以下操作:
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)
但是如果这种情况在您的测试套件中太常见,我建议编写(例如,be\u near\u time\u now\u db\u string
)将db string time转换为time对象,然后将其用作匹配(哈希)
的一部分:
我发现解决这个问题的最简单方法是创建一个current\u time
testhelper方法,如下所示:
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
因为我在比较散列,这些解决方案中的大多数都不适用于我,所以我发现最简单的解决方案就是从我比较的散列中简单地获取数据。因为更新的_有时对我来说并没有实际的用处,所以我可以很好地测试它
data = { updated_at: Date.new(2019, 1, 1,), some_other_keys: ...}
expect(data).to eq(
{updated_at: data[:updated_at], some_other_keys: ...}
)
它正在比较对象ID,因此来自inspect的文本是匹配的,但是下面有两个不同的时间对象。您可以只使用==
,但这可能会因为跨越第二个边界而受到影响。也许最好是找到或编写自己的匹配器,其中转换为纪元秒,并允许一个小的绝对差异。如果我理解你关于“跨越秒边界”的说法,那么问题应该不会出现,因为我使用的是“冻结”时间的宝石。啊,我错过了,对不起。在这种情况下,只需使用===
而不是=
——当前您正在比较两个不同时间对象的对象id。虽然Timecop不会冻结数据库服务器时间。因此,如果您的时间戳是由RDBMS生成的,那么它将不起作用(我希望这对您来说不是问题),正如您在问题代码中看到的,我使用Timecop
gem。它应该通过“冻结”时间来解决问题吗?您可以将时间保存在数据库中并检索它(@article.updated_at),这会使它失去纳秒,而as time.now保留纳秒。从我的回答的前几行可以清楚地看出,公认的答案比下面的答案早了将近一年——matcher中的be_是一种更好的处理方法:a)你不需要宝石;B) 它适用于任何类型的值(整数、浮点、日期、时间等);C) 这是rspect的固有部分这是一个“OK”解决方案,但肯定是be\u in
是正确的解决方案您好,我正在使用与作业排队延迟预期的日期时间比较expect{FooJob.perform\u now}。让作业(FooJob)排队。在(某个时间)
我不确定.at
匹配器是否会接受使用转换为整数的时间。要想知道是否有人遇到过这个问题?.000001秒有点紧。我甚至尝试了.001,但有时失败了。我认为,即使是1秒,也证明了时间被设定为现在。就我的目的而言,这已经足够好了。我认为这个答案更好,因为它表达了测试的意图,而不是用一些不相关的方法来掩盖它,比如to\s
。此外,如果时间接近一秒,则到i
和到s
可能很少失败。看起来be\u in
匹配器于2010年11月7日添加到RSpec 2.1.0中,并从那时起进行了几次增强。我得到了1:Fixnum的未定义的方法“second”。有什么事吗
expect({mytime: Time.now}).to match({mytime: be_within(3.seconds).of(Time.now)}) `
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)
expect(hash).to match({mytime: be_near_time_now_db_string}) # you need to write your own matcher for this to work.
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
data = { updated_at: Date.new(2019, 1, 1,), some_other_keys: ...}
expect(data).to eq(
{updated_at: data[:updated_at], some_other_keys: ...}
)