无法将时间与RSpec进行比较

 BREW微博2602931837 发布于 2023-02-12 17:04

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

Oin.. 183

我发现使用be_within默认的rspec匹配器更优雅:

expect(@article.updated_at.utc).to be_within(1.second).of Time.now

我认为这个答案更好,因为它表达了测试的意图,而不是在一些不相关的方法(如`to_s`)背后隐藏它.此外,如果时间接近一秒结束,`to_i`和`to_s`可能不经常失败. (3认同)

@DavidMoles` .second`是一个rails扩展:http://api.rubyonrails.org/classes/Numeric.html (3认同)

.000001 s有点紧张.我甚至尝试过.001并且它有时失败了.甚至.1秒,我认为证明时间已经到了.足够我的目的. (2认同)

看起来`be_within`匹配器已于2010年11月7日添加到RSpec 2.1.0,并从那时起增强了几次.http://rspec.info/documentation/2.14/rspec-expectations/file.Changelog.html (2认同)


usha.. 147

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)

请参阅本关于为什么时间是不同的更多信息

6 个回答
  • 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)
    

    请参阅本关于为什么时间是不同的更多信息

    2023-02-12 17:05 回答
  • 老帖子,但我希望它可以帮助任何进入这里寻求解决方案的人.我认为手动创建日期更简单,更可靠:

    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或担心小数.

    2023-02-12 17:05 回答
  • 我发现这个问题的最简单方法是创建一个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
    

    2023-02-12 17:06 回答
  • 您可以将日期/日期时间/时间对象转换为字符串,因为它存储在数据库中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)
    

    2023-02-12 17:06 回答
  • 我发现使用be_within默认的rspec匹配器更优雅:

    expect(@article.updated_at.utc).to be_within(1.second).of Time.now
    

    2023-02-12 17:06 回答
  • 是的,因为Oin建议be_within匹配是最好的做法

    ...它有更多的用户 - > http://www.eq8.eu/blogs/27-rspec-be_within-matcher

    但是,如何处理这个问题的另一种方法是使用内置的Rails middaymiddnight属性.

    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调用=>所有时间属性都有相同的时间=>可能无法证明你正试图实现的概念.我通常在组合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
    

    但老实说,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)}) `
    

    您可以将任何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)
    

    但是,如果你的测试套件中经常出现这种情况,我建议编写自己的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.
    

    2023-02-12 17:06 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有