Reputation: 618
I have a controller which destroys an item in my db. Currently it looks like this:
before_filter(:except => :toggle_item_owned_state) do
@collection = current_user.collections.find(params[:collection_id])
end
def destroy
@item = @collection.items.find_by_id(params[:id])
if @item.destroy
redirect_to collection_items_path(@collection)
else
flash.now[:alert] = "There was a problem deleting this item."
redirect_to root_path
end
end
Now, I've written a few rspec controller tests to verify the happy path, but I'd like like to test the failure path (ie when @item.destroy fails). I would imagine the correct way to do this is using some kind of mocking or stubbing, but I can't come up with something that works.
I've tried the following with some variations, but it's not working:
context "delete fails" do
before(:each) do
allow(@item).to receive(:destroy).and_return(false)
delete :destroy, :collection_id => batman_collection.id, :id => item_in_collection.id
end
it "will generate a flash error message" do
expect(flash[:alert]).to eq("There was a problem saving your collection.")
end
end
If anyone out there can provide me some direction or sample code on how to do this, it would be appreciated.
Thanks
Upvotes: 0
Views: 2078
Reputation: 11076
How are you setting @item
in the spec? I suspect it's not actually being stubbed.
Update:
Without seeing your controller, I can't give the exact code, but normally it would be something like this:
item = double
allow(Item).to receive(:find).and_return(item)
allow(item).to receive(:destroy).and_return(false)
Update 2:
Expanding out, you set item
with:
current_user.collections.find(params[:collection_id]).items.find_by_id(params[:id])
This is a very long chain of calls. RSpec has ways of dealing this, but they are in a section called Working with legacy code which says Usage of these features should be considered a code smell.
One approach to improve the code could be to introduce a service object:
class FindItem
def self.call(item_id, collection_id, user)
user.collections.find(params[:collection_id]).items.find_by_id(item)
end
end
This is much simpler to stub, and helps to decouple the controller from the DB structure. destroy
can now be stubbed with:
item = double
allow(FindItem).to receive(:call).and_return(item)
allow(item).to receive(:destroy).and_return(false)
Upvotes: 3