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)
Oin
はbe_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