I have an Invoice model that may contain a number of Items:
class Invoice < ActiveRecord::Base
attr_accessible :number, :date, :recipient, :items_attributes
belongs_to :user
has_many :items
accepts_nested_attributes_for :items, :reject_if => :all_blank, :allow_destroy => true
end
I am trying to test this using RSpec:
describe InvoicesController do
describe 'user access' do
before :each do
@user = FactoryGirl.create(:user)
@invoice = @user.invoices.create(FactoryGirl.attributes_for(:invoice))
sign_in(@user)
end
it "renders the :show view" do
get :show
expect(response).to render_template :show
end
end
end
Unfortunately, this test (and all the others) fail with this error message from RSpec:
Failure/Error: @invoice = @user.invoices.create(FactoryGirl.attributes_for(:invoice))
ActiveModel::MassAssignmentSecurity::Error:
Can't mass-assign protected attributes: items
How can I create an invoice with items that will pass my tests?
I am using FactoryGirl to fabricate objects like this:
factory :invoice do
number { Random.new.rand(0..1000000) }
recipient { Faker::Name.name }
date { Time.now.to_date }
association :user
items { |i| [i.association(:item)] }
end
factory :item do
date { Time.now.to_date }
description { Faker::Lorem.sentences(1) }
price 50
quantity 2
end
-To use nested attributes in your example you need to pass in "item_attributes" and not "items" like you are currently doing.
I'm not fluent in FactoryGirl, but maybe something along these lines would work? :
invoice_attributes = FactoryGirl.attributes_for(:invoice)
invoice_attributes["item_attributes"] = invoice_attributes["items"]
invoice_attributes["items"] = nil
@invoice = @user.invoices.create(invoice_attributes)
That s should hopefully simulate what parameters get passed in from your form.
Edit: Misunderstood the question. Apologies.
Instead of
before :each do
@user = FactoryGirl.create(:user)
@invoice = @user.invoices.create(FactoryGirl.attributes_for(:invoice))
sign_in(@user)
end
Simply create the factories for invoice passed with a user param, like so:
before :each do
@user = FactoryGirl.create(:user)
FactoryGirl.create :invoice, user: @user
sign_in(@user)
end
Also, this a minor style suggestion, but instead of instance variables, you can use lets, like so:
let(:user) { FactoryGirl.create :user }
before :each do
FactoryGirl.create :invoice, user: user
sign_in(user)
end
Passing 'user' in to the invoice create will also create the user (and it is callable as simply 'user').
Minor caveat: I've been doing this for about 6 months, so there might be someone more knowledgeable who disagrees with my style suggestion.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With