Agazoom
Agazoom

Reputation: 618

Testing destroy method failure in Controller test

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

Answers (1)

Andy Waite
Andy Waite

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

Related Questions