【问题标题】:How to test Rails create action with nested attributes and FactoryGirl?如何使用嵌套属性和 FactoryGirl 测试 Rails 创建操作?
【发布时间】:2013-03-08 12:48:35
【问题描述】:

我有一个接受invoice 及其嵌套items 的模型:

class Invoice < ActiveRecord::Base

  belongs_to :user
  has_many :items

  attr_accessible :number, :date, :recipient, :project_id, :items_attributes

  accepts_nested_attributes_for :items, :reject_if => :all_blank

end

不过,我发现用 RSpec 和 FactoryGirl 测试它非常困难。这就是我所拥有的:

describe 'POST #create' do

  context "with valid attributes" do

    it "saves the new invoice in the database" do
      expect {
        post :create, invoice: attributes_for(:invoice), items_attributes: [ attributes_for(:item), attributes_for(:item) ]
      }.to change(Invoice, :count).by(1)        
    end

  end

end

这是我在控制器中的创建操作:

def create
  @invoice = current_user.invoices.build(params[:invoice])
  if @invoice.save
    flash[:success] = "Invoice created."
    redirect_to invoices_path
  else
    render :new
  end
end

每当我运行它时,我都会收到一个错误:Can't mass-assign protected attributes: items

有人可以帮我解决这个问题吗?

谢谢...

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-3 rspec ruby-on-rails-3.2 factory-bot


    【解决方案1】:

    首先:items 是嵌套的,所以它们在 params 中的名称是 items_attributes。改变它。

    第二:嵌套意味着...嵌套!

    基本上,替换:

    post :create, invoice: attributes_for(:invoice, items: [ build(:item), build(:item) ])
    

    与:

    post :create, invoice: { attributes_for(:invoice).merge(items_attributes: [ attributes_for(:item), attributes_for(:item) ]) }
    

    SideNote,你在这里做的是真正的集成测试,你可以存根来保持单元测试。

    【讨论】:

    • 啊,看起来比我的版本好多了,谢谢。但是,您的行在这里会导致语法错误,因此我删除了圆括号。这样,测试运行但仍然抛出相同的错误:Can't mass-assign protected attributes: items
    • 语法正确,参见参考:api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/…。我写的参数中没有item,我猜你拼错了什么
    • 嗯,奇怪,我不得不再次删除圆括号才能让它工作。现在测试运行,但抛出与以前相同的错误。因为我只是复制了你的行,所以没有任何拼写错误的机会。
    • 我猜 atributes_for 引入了不需要的属性,你能复制粘贴控制器收到的参数吗?
    • 抱歉这个愚蠢的问题,但我可以从哪里得到这些参数?我可能通过更改我的 FactoryGirl 文件找到了解决方案。我现在得走了,但如果你以后能再看看这个帖子,那就太好了。
    【解决方案2】:

    我遇到了同样的问题,所以我创建了一个补丁,将 FactoryGirl.nested_attributes_for 方法添加到 FactoryGirl:

    module FactoryGirl
      def self.nested_attributes_for(factory_sym)
        attrs = FactoryGirl.attributes_for(factory_sym)
        factory = FactoryGirl.factories[factory_sym]
        factory.associations.names.each do |sym|
          attrs["#{sym}_attributes"] = FactoryGirl.attributes_for sym
        end
        return attrs
      end
    end
    

    所以现在你可以打电话了:

    post :create, invoice: FactoryGirl.nested_attributes_for(:invoice) }
    

    你会得到你所知道和喜爱的所有嵌套形式的好处:)

    (要应用补丁,您需要将我答案顶部的代码复制到 config/initializers 文件夹中的新文件中)

    【讨论】:

    • 这对我来说效果很好。我认为这应该是公认的答案。感谢伟大的代码
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-17
    • 1970-01-01
    • 1970-01-01
    • 2015-03-17
    • 2014-06-28
    相关资源
    最近更新 更多