web-dev-qa-db-ja.com

時間とRSpecの比較に関する問題

Ruby 4とrspec-Rails gem 2.14でRailsを使用しています。私のオブジェクトの場合、コントローラーアクションの実行後に現在の時刻を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

しかし、仕様はまだ通過しません( "got"値の違いに注意してください):

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 ==)
110
Backo

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

142
usha

be_withinデフォルトのrspecマッチャーを使用すると、よりエレガントになります。

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

古い投稿ですが、解決策を求めてここに入る人には役立つと思います。日付を手動で作成する方が簡単で信頼性が高いと思います。

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を実行したり、小数を心配したりすることなく、保存された日付が正しい日付になります。

8
jBilbo

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

...さらにいくつかのケースがあります-> http://www.eq8.eu/blogs/27-rspec-be_within-matcher

しかし、これに対処するもう1つの方法は、middayおよびmiddnight属性に組み込まれた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呼び出しをスタブ化しているため、コントローラーではこれを使用しません=>すべての時間属性が同じ時間を持ちます=>達成しようとしている概念が証明されない場合があります。私は通常、これに似た構成された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 matcherを使用することをお勧めします。

はい、plsはbe_within matcherに固執します;)


2017-02を更新

コメントの質問:

時間がハッシュ内にある場合はどうなりますか? hash_1値の一部がdb-timeであり、hash_2の対応する値がpost-db-timeであるときに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オブジェクトに変換する own RSpec matcher (eg be_near_time_now_db_string)を記述し、これを一部として使用することをお勧めします。 match(hash)

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

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)
7
Thomas Klemm

この問題を回避する最も簡単な方法は、次のような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
Sam Davies