Bac*_*cko 113 ruby comparison time rspec ruby-on-rails
我正在使用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
Run Code Online (Sandbox Code Playgroud)
当我运行上面的规范时,我收到以下错误:
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 ==)
Run Code Online (Sandbox Code Playgroud)
如何使规范通过?
注意:我还尝试了以下(注意utc添加):
it "updates updated_at attribute" do
Timecop.freeze
patch :update
@article.reload
expect(@article.updated_at.utc).to eq(Time.now)
end
Run Code Online (Sandbox Code Playgroud)
但规格仍然没有通过(注意"得到"的价值差异):
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 ==)
Run Code Online (Sandbox Code Playgroud)
Oin*_*Oin 183
我发现使用be_within默认的rspec匹配器更优雅:
expect(@article.updated_at.utc).to be_within(1.second).of Time.now
Run Code Online (Sandbox Code Playgroud)
ush*_*sha 147
Ruby Time对象保持比数据库更高的精度.当从数据库中读回值时,它仅保留为微秒精度,而内存中表示精确到纳秒.
如果你不关心毫秒差异,你可以在期望的两边做一个to_s/to_i
expect(@article.updated_at.utc.to_s).to eq(Time.now.to_s)
Run Code Online (Sandbox Code Playgroud)
要么
expect(@article.updated_at.utc.to_i).to eq(Time.now.to_i)
Run Code Online (Sandbox Code Playgroud)
请参阅本关于为什么时间是不同的更多信息
老帖子,但我希望它可以帮助任何进入这里寻求解决方案的人.我认为手动创建日期更简单,更可靠:
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
Run Code Online (Sandbox Code Playgroud)
这样可以确保存储的日期是正确的,而不会产生to_x或担心小数.
您可以将日期/日期时间/时间对象转换为字符串,因为它存储在数据库中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)
Run Code Online (Sandbox Code Playgroud)
是的,因为Oin建议be_within匹配是最好的做法
...它有更多的用户 - > http://www.eq8.eu/blogs/27-rspec-be_within-matcher
但是,如何处理这个问题的另一种方法是使用内置的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
Run Code Online (Sandbox Code Playgroud)
现在这只是为了演示!
我不会在控制器中使用它,因为你正在对所有Time.new调用=>所有时间属性都有相同的时间=>可能无法证明你正试图实现的概念.我通常在组合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
Run Code Online (Sandbox Code Playgroud)
但老实说,be_within即使比较Time.now.midday,我也建议坚持使用matcher!
所以是的,请坚持使用be_within匹配器;)
更新2017-02
评论中的问题:
如果时间在哈希?任何使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)}) `
Run Code Online (Sandbox Code Playgroud)
您可以将任何RSpec匹配器传递给match匹配器(例如,您甚至可以使用纯RSpec进行API测试)
对于"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)
Run Code Online (Sandbox Code Playgroud)
但是,如果你的测试套件中经常出现这种情况,我建议编写自己的RSpec匹配器(例如be_near_time_now_db_string)将db字符串时间转换为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.
Run Code Online (Sandbox Code Playgroud)
我发现这个问题的最简单方法是创建一个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
Run Code Online (Sandbox Code Playgroud)
现在时间总是四舍五入到最接近的毫秒,比较很简单:
it "updates updated_at attribute" do
Timecop.freeze(current_time)
patch :update
@article.reload
expect(@article.updated_at).to eq(current_time)
end
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
44243 次 |
| 最近记录: |