代码之家  ›  专栏  ›  技术社区  ›  lobati

与rspec的Rails时间不一致

  •  22
  • lobati  · 技术社区  · 14 年前

    start_date ||= Time.now
    end_date = start_date + goal_months.months
    

    然后我克隆这个对象,并编写rspec测试来确认副本中的属性是否匹配。 结束日期匹配:

    original[end_date]:  2011-08-24 18:24:53 UTC
    clone[end_date]:     2011-08-24 18:24:53 UTC
    

    expected: Wed Aug 24 18:24:53 UTC 2011,
         got: Wed, 24 Aug 2011 18:24:53 UTC +00:00 (using ==)
    

    很明显日期是一样的,只是格式不同而已。它们最终如何以不同的方式存储在数据库中,我如何使它们匹配?我用DateTime也试过了,结果一样。

    更正:

    start date: 2010-08-24T19:00:24+00:00
    end date: 2011-08-24 19:00:24 UTC
    
    10 回复  |  直到 14 年前
        1
  •  23
  •   robertokl    14 年前

    您应该模拟now时间方法,以确保它始终与规范中的日期相匹配。您永远不知道什么时候延迟会导致规范失败,因为有几毫秒。这种方法还将确保实际代码和规范上的时间是相同的。

    如果您使用的是默认的rspec mock lib,请尝试执行以下操作:

    t = Time.parse("01/01/2010 10:00")
    Time.should_receive(:now).and_return(t)
    
        2
  •  52
  •   cutalion    13 年前

    这通常是因为rspec尝试匹配不同的对象:例如Time和DateTime。同样,可比较的时间可能会有一些不同,比如几毫秒。

    TimeCop gem

    在第一种情况下,可能的解决方案是比较时间戳:

    actual_time.to_i.should == expected_time.to_i
    

    我用简单的 matcher 对于此类情况:

    # ./spec/spec_helper.rb
    #
    # Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each {|f| require f}
    #
    #
    # Usage:
    #
    # its(:updated_at) { should be_the_same_time_as updated_at }
    #
    #
    # Will pass or fail with message like:
    #
    # Failure/Error: its(:updated_at) { should be_the_same_time_as 2.days.ago }
    # expected Tue, 07 Jun 2011 16:14:09 +0300 to be the same time as Mon, 06 Jun 2011 13:14:09 UTC +00:00
    
    
    RSpec::Matchers.define :be_the_same_time_as do |expected|
      match do |actual|
        expected.to_i == actual.to_i
      end
    end
    
        3
  •  21
  •   DrewB    12 年前

    我完全同意前面关于存根的回答时间到了。现在. 也就是说这里还有一件事。当您从数据库中比较DateTime时,您会损失一些ruby DateTime对象中的派系时间。在Rspec中以这种方式比较日期的最佳方法是:

    database_start_date.should be_within(1).of(start_date)
    
        4
  •  8
  •   Guillermo Siliceo Trueba    10 年前

    其中一个问题是Ruby时间对象的精度是纳秒,但大多数数据库的精度是微秒。最好的办法就是去存根时间到了。现在(或使用timecop)和一个整数。阅读我写的帖子: http://blog.solanolabs.com/rails-time-comparisons-devil-details-etc/

        5
  •  0
  •   Toby Stafford    14 年前

        6
  •  0
  •   zetetic    14 年前

    == 而不是 eql be ? 后两种方法使用对象标识,而不是比较值。

    从输出来看,期望值似乎是 Time ,而被测试的值是 DateTime . 这也可能是一个问题,不过考虑到Ruby的日期和时间库几乎是病态的,我不知道如何修复它。。。

        7
  •  0
  •   lobati    11 年前

    我喜欢的一个解决方案是在 spec_helper :

    class Time
      def ==(time)
        self.to_i == time.to_i
      end
    end
    

    这样即使在嵌套对象中也完全透明。

        8
  •  0
  •   scarver2    11 年前

    .to_datetime 对于这两个变量,将强制datetime值相等,并遵守时区和夏令时。对于日期比较,请使用 .to_date .

    actual_time.to_datetime.should == expected_time.to_datetime

    更清晰的规格: actual_time.to_datetime.should eq 1.month.from_now.to_datetime

    .to_i

    +1用于在规格中使用TimeCop gem。只要确保测试夏令时在您的规格,如果你的应用程序是受夏令时。

        9
  •  0
  •   lobati    9 年前

    我们目前的解决办法是 freeze_time

    def freeze_time(time = Time.zone.now)
      # round time to get rid of nanosecond discrepancies between ruby time and
      # postgres time
      time = time.round
      Timecop.freeze(time) { yield(time) }
    end
    

    然后你可以像这样使用它:

    freeze_time do
      perform_work
      expect(message.reload.sent_date).to eq(Time.now)
    end
    
        10
  •  0
  •   Halil Özgür    7 年前

    根据您的规格,您可能可以使用Rails native travel helpers

    # in spec_helper.rb
    config.include ActiveSupport::Testing::TimeHelpers
    
    start_date ||= Time.current.change(usecs: 0)
    end_date = start_date + goal_months.months
    travel_to start_date do
      # Clone here  
    end
    expect(clone.start_date).to eq(start_date)
    

    Time.current.change(usecs: 0) 它可能会抱怨时区之间的差异。或者在微秒之间,因为helper将在内部重置传递的值(Timecop也有类似的问题,所以reset usecs 有了它)。