在Controller测试中测试破坏方法失败

时间:2015-05-26 23:06:20

标签: ruby-on-rails rspec

我有一个控制器会破坏我的数据库中的项目。目前它看起来像这样:

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

现在,我已经编写了一些rspec控制器测试来验证快乐路径,但我想测试失败路径(即@ item.destroy失败时)。我认为这样做的正确方法是使用某种嘲弄或抄写,但我无法想出一些有用的东西。

我已经尝试了以下某些变体,但它不起作用:

        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

如果那里的任何人可以为我提供一些指导或示例代码,我们将不胜感激。

由于

1 个答案:

答案 0 :(得分:3)

您如何在规范中设置@item?我怀疑它实际上并没有被打断。

更新

在没有看到你的控制器的情况下,我无法提供确切的代码,但通常它会是这样的:

item = double
allow(Item).to receive(:find).and_return(item)
allow(item).to receive(:destroy).and_return(false)

更新2:

展开,您将item设置为:

current_user.collections.find(params[:collection_id]).items.find_by_id(params[:id])

这是一个非常长的电话链。 RSpec有办法解决这个问题,但它们在一个名为Working with legacy code的部分中,其中说使用这些功能 应被视为代码气味

改进代码的一种方法是引入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

这对于存根来说要简单得多,并且有助于将控制器与数据库结构分离。 destroy现在可以用:

item = double
allow(FindItem).to receive(:call).and_return(item)
allow(item).to receive(:destroy).and_return(false)
相关问题