ruby-on-rails - 比較 - rspec 日付




RSpecと時間を比較する際の問題 (4)

私は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追加に注意して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 ==)

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を参照しthisください


Oinbe_withinマッチャーがベストプラクティスであると示唆している

...そしてそれはいくつかのuscasesを持っています - > http://www.eq8.eu/blogs/27-rspec-be_within-matcher

しかしこれに対処するもう一つの方法は、 midday構築されたRailsと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

今これはデモンストレーションのためのものです!

あなたがすべての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比較してもbe_within matcherをbe_withinことをお勧めします!

だからはいplsはbe_within matcherとスティック;)

アップデート2017-02

コメントの質問:

時がハッシュにあればどうなりますか? hash_1の値がpre-db-timesで、hash_2の対応する値がpost-db-timesの場合、expect(hash_1).to eq(hash_2)を動作させる方法はありますか? -

expect({mytime: Time.now}).to match({mytime: be_within(3.seconds).of(Time.now)}) `

どのRSpecマッチャーをmatchマッチャーにも渡すことができます(例えば、純粋なRSpecでAPIテストを行うこともできます

「post-db-times」に関しては、DBに保存した後に生成される文字列を意味すると思います。 私はこのケースを2つの期待に分解することを提案します(1つはハッシュ構造を保証し、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)

しかし、このケースがあなたのテストスイートであまりにも多い場合は、DB文字列の時刻をTimeオブジェクトに変換して、これをmatch(hash)一部として使用する独自のRSpecマッチャー (例be_near_time_now_db_string )を書くことをお勧めします。

 expect(hash).to match({mytime: be_near_time_now_db_string})  # you need to write your own matcher for this to work.

古い投稿ですが、私はそれが解決のためにここに入った人を助けることを願っています。 手動で日付を作成する方が簡単で信頼性が高いと思います。

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や小数点以下のto_xずに、格納された日付が正しい日付になります。


私がこの問題の周りで見つけた最も簡単な方法は、 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




comparison