19

DateTime.now を使用していくつかのデータの検索を実行するメソッドがあります。さまざまな日付でメソッドをテストしたいのですが、DateTime.now をスタブ化する方法がわかりませんし、Timecop で動作させることもできません (そのように動作します)。

私が試した時間の警官で

it 'has the correct amount if falls in the previous month' do
      t = "25 May".to_datetime
      Timecop.travel(t)
      puts DateTime.now

      expect(@employee.monthly_sales).to eq 150
end 

仕様を実行すると、 puts DateTime.now が得られることがわかります2015-05-25T01:00:00+01:00が、出力をテストしているメソッド内に同じ puts DateTime.now があることがわかります2015-07-24T08:57:53+01:00(今日の日付)。どうすればこれを達成できますか?

- - - - - - - - - アップデート - - - - - - - - - - - - - - - - --------------------

before(:all)問題の原因と思われるブロックにレコード (@employee など) を設定していました。Timecop doブロックの後にセットアップが完了した場合にのみ機能します。これはなぜですか?

4

3 に答える 3

14

TL;DR : 問題は、仕様で呼び出される前に呼び出されたことDateTime.nowですEmployeeTimecop.freeze

Timecop はTimeDateおよびのコンストラクターをモックしDateTimeます。freezeとの間return(またはfreezeブロック内)で作成されたインスタンスはすべてモックされます。Timecop は既存のオブジェクトをいじらないため、前後に作成されたインスタンスは影響を受けません
freezereturn

READMEから(私の強調):

「タイムトラベル」と「タイムフリーズ」機能を提供する宝石で、時間に依存するコードのテストを非常に簡単にします。1 回の呼び出しで Time.now、Date.today、DateTime.now をモックする統一されたメソッドを提供します。

したがって、モックしたいオブジェクトをTimecop.freeze作成する前に呼び出すことが不可欠です。RSpecブロックにいるTime場合、これはが評価される前に実行されます。ただし、サブジェクトを設定するブロックがあり (あなたの場合)、ネストされた に別のブロックがある場合、サブジェクトは既に設定されており、時間が凍結する前に呼び出されます。freezebeforesubjectbefore@employeebeforedescribeDateTime.new


以下を追加するとどうなりますかEmployee

class Employee
  def now
    DateTime.now
  end
end

次に、次の仕様を実行します。

describe '#now' do
  let(:employee) { @employee }
  it 'has the correct amount if falls in the previous month', focus: true do
    t = "25 May".to_datetime
    Timecop.freeze(t) do
      expect(DateTime.now).to eq t
      expect(employee.now).to eq t

      expect(employee.now.class).to be DateTime
      expect(employee.now.class.object_id).to be DateTime.object_id
    end
  end
end

freezeブロックを使用する代わりに、rspecfreezeとフックで次のこともできます。returnbeforeafter

describe Employee do
  let(:frozen_time) { "25 May".to_datetime }
  before { Timecop.freeze(frozen_time) }
  after { Timecop.return }
  subject { FactoryGirl.create :employee }

  it 'has the correct amount if falls in the previous month' do
    # spec here
  end

end

トピック外ですが、http://betterspecs.org/ をご覧ください。

于 2015-07-28T16:07:49.757 に答える
3

Timecop は、必要なものを処理できる必要があります。ただ旅行するのではなく、テストを実行する前に時間を凍結し、終了したら凍結を解除してください。このような:

before do
  t = "25 May".to_datetime
  Timecop.freeze(t)
end

after do
  Timecop.return
end

it 'has the correct amount if falls in the previous month' do
  puts DateTime.now
  expect(@employee.monthly_sales).to eq 150
end 

Timecop の readme から:

フリーズは、現在の概念を静的にモックするために使用されます。プログラムが実行されても、Timecop API に後続の呼び出しを行わない限り、Time.now は変更されません。一方、travel は、現在 Time.now と考えられているもの (ネストされた旅行をサポートしていることを思い出してください) と渡された時間との間のオフセットを計算します。このオフセットを使用して、時間の経過をシミュレートします。

そのため、単にその時間に移動するのではなく、特定の場所で時間を凍結したいと考えています。時間は通常の旅と同じように過ぎていきますが、出発点は異なります。

これでもうまくいかない場合は、Timecop を使用してメソッド呼び出しをブロックに配置し、次のようにブロック内の時間を凍結するようにすることができます。

t = "25 May".to_datetime
Timecop.travel(t) do # Or use freeze here, depending on what you need
  puts DateTime.now
  expect(@employee.monthly_sales).to eq 150
end
于 2015-07-27T16:13:53.160 に答える