web-dev-qa-db-ja.com

RSpecで「any_instance」「should_receive」を何度でも言う方法

Railsに複数のレコードを持つ複数のcsvファイルをデータベースにインポートするインポートコントローラーがあります。レコードがRSpecを使用して実際に保存されているかどうかをRSpecでテストしたいです。

<Model>.any_instance.should_receive(:save).at_least(:once)

しかし、次のようなエラーが表示されます:

The message 'save' was received by <model instance> but has already been received by <another model instance>

コントローラーの不自然な例:

rows = CSV.parse(uploaded_file.tempfile, col_sep: "|")

  ActiveRecord::Base.transaction do
    rows.each do |row| 
    mutation = Mutation.new
    row.each_with_index do |value, index| 
      Mutation.send("#{attribute_order[index]}=", value)
    end
  mutation.save          
end

RSpecを使用してこれをテストすることは可能ですか、または回避策はありますか?

65
Harm de Wit

これには新しい構文があります:

expect_any_instance_of(Model).to receive(:save).at_least(:once)
45
muirbot

:newメソッドをオーバーライドする必要を回避するより良い答えは次のとおりです。

save_count = 0
<Model>.any_instance.stub(:save) do |arg|
    # The evaluation context is the rspec group instance,
    # arg are the arguments to the function. I can't see a
    # way to get the actual <Model> instance :(
    save_count+=1
end
.... run the test here ...
save_count.should > 0

スタブメソッドは、制約なしの任意のインスタンスにアタッチできるようで、doブロックは、正しい回数と呼ばれることを確認するためにチェックできるカウントを作成できるようです。

更新-新しいrspecバージョンには次の構文が必要です。

save_count = 0
allow_any_instance_of(Model).to receive(:save) do |arg|
    # The evaluation context is the rspec group instance,
    # arg are the arguments to the function. I can't see a
    # way to get the actual <Model> instance :(
    save_count+=1
end
.... run the test here ...
save_count.should > 0
45
Rob

私は最終的に自分に合ったテストを作成することができました:

  mutation = FactoryGirl.build(:mutation)
  Mutation.stub(:new).and_return(mutation)
  mutation.should_receive(:save).at_least(:once)

スタブメソッドは、saveメソッドを複数回受信する単一のインスタンスを返します。単一のインスタンスなので、any_instanceメソッドとat_leastメソッドは通常。

15
Harm de Wit

このようなスタブ

User.stub(:save) # Could be any class method in any class
User.any_instance.stub(:save) { |*args| User.save(*args) }

次に、このように期待します:

# User.any_instance.should_receive(:save).at_least(:once)
User.should_receive(:save).at_least(:once)

これは、 このGist の簡略化であり、any_instance、元のメソッドにプロキシする必要がないため。他の用途については、そのGistを参照してください。

10
michelpm

これはRSpec 3.3を使用したRobの例であり、Foo.any_instanceをサポートしなくなりました。これは、ループ内でオブジェクトを作成するときに役立ちます

# code (simplified version)
array_of_hashes.each { |hash| Model.new(hash).write! }

# spec
it "calls write! for each instance of Model" do 
  call_count = 0
  allow_any_instance_of(Model).to receive(:write!) { call_count += 1 }

  response.process # run the test
  expect(call_count).to eq(2)
end
8
sp89

私の場合は少し違っていましたが、私もこの質問に答えて、答えをここに落とすことにしました。私の場合、特定のクラスのインスタンスをスタブ化したかったのです。 expect_any_instance_of(Model).toを使用したときに同じエラーが発生しました。 allow_any_instance_of(Model).toに変更すると、問題は解決しました。

さらなる背景についてはドキュメントをご覧ください: https://github.com/rspec/rspec-mocks#settings-mocks-or-stubs-on-any-instance-of-a-class

2
Rick Pastoor

クラスのnewの数を数えてみてください。実際にはsavesの数をテストするわけではありませんが、十分かもしれません

    expect(Mutation).to receive(:new).at_least(:once)

それが何回保存されたかについての唯一の期待がある場合。 Harm de Wit自身の答えのように、おそらく完全に機能するファクトリの代わりにspy()を使用したいでしょう。

    allow(Mutation).to receive(:new).and_return(spy)
    ...
    expect(Mutation.new).to have_received(:save).at_least(:once)
0
Brazhnyk Yuriy