无法将时间与RSpec进行比较

时间:2013-12-05 14:49:03

标签: ruby-on-rails ruby time rspec comparison

我正在使用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 ==)

7 个答案:

答案 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

但是,如何处理这个问题的另一种方法是使用内置middaymiddnight属性的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: ...}
)