I've got an import controller in rails that imports several csv files with multiple records into my database. I would like to test in RSpec if the records are actually saved by using RSpec:
<Model>.any_instance.should_receive(:save).at_least(:once)
However i get the error saying:
The message 'save' was received by <model instance> but has already been received by <another model instance>
A contrived example of the controller:
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
Is it possible to test this using RSpec or is there any workaround?
Here's a better answer that avoids having to override the :new method:
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
Seems that the stub method can be attached to any instance w/o the constraint, and the do block can make a count that you can check to assert it was called the right number of times.
Update - new rspec version requires this syntax:
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
There's a new syntax for this:
expect_any_instance_of(Model).to receive(:save).at_least(:once)
expect_any_instance_of
. Here's a link to the doc.
I finally managed to make a test that works for me:
mutation = FactoryGirl.build(:mutation)
Mutation.stub(:new).and_return(mutation)
mutation.should_receive(:save).at_least(:once)
The stub method returns one single instance that receives the save method multiple times. Because it is a single instance i can drop the any_instance
method and use the at_least
method normally.
This is Rob's example using RSpec 3.3, which no longer supports Foo.any_instance
. I found this useful when in a loop creating objects
# 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
Stub like this
User.stub(:save) # Could be any class method in any class
User.any_instance.stub(:save) { |*args| User.save(*args) }
Then expect like this:
# User.any_instance.should_receive(:save).at_least(:once)
User.should_receive(:save).at_least(:once)
This is a simplification of this gist, to use any_instance
, since you don't need to proxy to the original method. Refer to that gist for other uses.
allow
and allow_any_instance_of
instead of stub
and any_instance.stub
since the former are now deprecated (see: github.com/rspec/…)
My case was a bit different, but I ended up at this question to figured to drop my answer here too. In my case I wanted to stub any instance of a given class. I got the same error when I used expect_any_instance_of(Model).to
. When I changed it to allow_any_instance_of(Model).to
, my problem was solved.
Check out the documentation for some more background: https://github.com/rspec/rspec-mocks#settings-mocks-or-stubs-on-any-instance-of-a-class
expect_any_instance_of(Widget).to receive(:name).twice
it isn't clear whether each specific instance is expected to receive name
twice, or if two receives total are expected. (It's the former.)"
You may try to count the number of new
on the class. That is not actually tests the number of save
s but may be enough
expect(Mutation).to receive(:new).at_least(:once)
If there is the only expectation of how many times it was saved. Then you probably want to use spy()
instead of fully functioning factory, as in Harm de Wit
own answer
allow(Mutation).to receive(:new).and_return(spy)
...
expect(Mutation.new).to have_received(:save).at_least(:once)
Success story sharing