简体   繁体   中英

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

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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM