Attributes_for Ruby on Rails 4 tests with factory girl - ruby-on-rails

I'm trying to test simple POST controller#create method.
it "saves the new object in the DB" do
object = build(:object_with_association)
expect { post :create, object: object.attributes }.to change(SomeObject, :count).by(1)
end
Is this object.attributes is the best way to do this? I tried to use attributes_for(:object_with_association), but it returns a hash without associations at all. May be there is some useful method to do this inside expect?
My factory:
factory :obj do
name "A"
association :first, factory: :first
association :second, factory: :second
factory :obj_with_association do
transient do
nested_count 5
end
after(:build) do |obj, evaluator|
create_list(:nested, evaluator.nested_count, obj: obj)
end
end
end
Association for the :first and :second is belongs_to, for the :nested is has_many

You can create attributes separately and merge them, or you can try traits with FactoryGirl.
Please take a look at these two SO questions for details:
factory girl nested factory
Using FactoryGirl to create a parameter string for controller testing with related models

Related

Rails: access test fixtures from factories?

Is there a way I can access test fixtures from within my factories? I'm trying to do something like:
Factory.define do
factory :foo do
user { users(:active) }
end
end
You can access fixtures in your factories using the ActiveRecord::FixtureSet class, which provides access to the fixtures defined in your test/fixtures directory. You could access the :active user fixture like this:
FactoryBot.define do
factory :foo do
user { ActiveRecord::FixtureSet.cached_fixtures[:active, 'users'] }
end
end
IMO the use of fixtures in factories is discouraged, as it couples your factories to your tests and makes it difficult to reuse your factories in other contexts. A better approach would be to create a factory for the User model with an active trait and use that within your :foo factory
FactoryBot.define do
factory :user do
trait :active do
active { true }
end
end
factory :foo do
user { create(:user, :active) }
end
end

Generating nested attributes for FactoryGirl objects with accepts_nested_attributes_for

I'm using RSpec and Factory Girl to test my application. What I'm trying to do is the following: I have a object which accepts nested attributes, and it is not valid that nested attribute. I want to test that the POST works:
let(:valid_attributes) { build(:user).attributes }
it "creates a new User" do
expect {
post :create, {user: valid_attributes}, valid_session
}.to change(User, :count).by(1)
end
That's the factory:
FactoryGirl.define do
factory :user do |x|
name "Homer"
after(:build) do
build :address
end
end
end
The problem is that the hash returned by build(:user).attributes does not have the address, although if I inspect the object created by build(:user), the address is correctly built.
Is there some way to easily generate a hash with the nested attributes?
Answering myself to show a technically working solution:
let(:valid_attributes) {attributes_for(:user, address_attributes: attributes_for(:address))}
This works, but I find it quite clunky. In complex cases the code would get incredibly verbose and ugly. As I would expect something nicer, I'm not voting this as a solution.
You can customize your object, while building it via parameters, so I'd solve your task this way:
let(:valid_attributes) { attributes_for(:user, address: attributes_for(:address)) }

Specifying associations to inherited factory with FactoryGirl

I've just started using FactoryGirl for my tests, and I've run into some issues.
I have user, qualification and user_qualification models. For qualifications, I have two inherited factories, namely qualification_ba and qualification_phd. Now, I'm trying to generate a qualification that includes either qualification_ba or qualification_phd factories by running:
create(:user_qualification_ba_bad_marks).
This returns:
NoMethodError: undefined method 'qualification_ba='.
However, when running:
create(:qualification_ba)
it successfully creates the object as expected. Is there perhaps a limit on how deep you can nest factories?
factory :user do
to_create {|instance| instance.save(validate: false) }
full_name Faker::Name.name
email Faker::Internet.email
password Faker::Internet.password(8)
factory :user_ba_candidate do
before(:create) do |user|
create_list(:user_qualification_ba_bad_marks, 1, user: user)
end
end
factory :user_phd_candidate do
before(:create) do |user|
create_list(:user_qualification_phd_good_marks, 1, user: user )
end
end
end
factory :qualification do
user_selectable true
factory :qualification_ba do
name "Bachelors"
qualification_level 2
end
factory :qualification_phd do
name "Masters"
qualification_level 5
end
end
factory :user_qualification do
to_create {|instance| instance.save(validate: false) }
factory :user_qualification_ba_bad_marks do
qualification_ba
aggregate (30..50).to_a.sample
end
factory :user_qualification_phd_good_marks do
qualification_phd
aggregate (70..90).to_a.sample
end
user
end

Associations with Factory Girl in Controller specs

I'm new to RSpec, and trying to get my head around using Factory Girl with associations in controller specs. The difficulty is:
it's necessary to use "attributes_for" in functional tests
attributes_for "elides any associations"
So if I have models like this:
class Brand < ActiveRecord::Base
belongs_to :org
validates :org, :presence => true
end
class Org < ActiveRecord::Base
has_many :brands
end
And a factory like this:
FactoryGirl.define do
factory :brand do
association :org
end
end
This controller spec fails:
describe BrandsController do
describe "POST create with valid params" do
it "creates a new brand" do
expect {
post :create, brand: attributes_for(:brand)
}.to change(Brand, :count).by(1)
end
end
end
(And if I comment out "validates :org, :presence => true" it passes)
There are a number of solutions suggested and I think I have been making simple errors which have meant that I have not been able to get any of them to work.
1) Changing the factory to org_id per a suggestion on this page failed a number of tests with "Validation failed: Org can't be blank"
FactoryGirl.define do
factory :brand do
org_id 1002
end
end
2) Using "symbolize_keys" looks promising. Here and here it is suggested to use code like this:
(FactoryGirl.build :position).attributes.symbolize_keys
I'm not sure how to apply this in my case. Below is a guess that doesn't work (giving the error No route matches {:controller=>"brands", :action=>"{:id=>nil, :name=>\"MyString\", :org_id=>1052, :include_in_menu=>false, :created_at=>nil, :updated_at=>nil}"}):
describe BrandsController do
describe "POST create with valid params" do
it "creates a new brand" do
expect {
post build(:brand).attributes.symbolize_keys
}.to change(Brand, :count).by(1)
end
end
end
Update
I almost got this working with Shioyama's answer below but got the error message:
Failure/Error: post :create, brand: build(:brand).attributes.symbolize_keys
ActiveModel::MassAssignmentSecurity::Error:
Can't mass-assign protected attributes: id, created_at, updated_at
So following this question I changed it to:
post :create, brand: build(:brand).attributes.symbolize_keys.reject { |key, value| !Brand.attr_accessible[:default].collect { |attribute| attribute.to_sym }.include?(key) }
Which worked!
In your solution 2), you have not passed an action to post which is why it is throwing an error.
Try replacing the code in that expect block to:
post :create, brand: build(:brand).attributes.symbolize_keys

How to test model with HABTM associations?

I need to test a model with HABTM association. My factory looks like:
FactoryGirl.define do
factory :article do |f|
body "Some awesome text"
after(:build) {|article| article.users = [create(:user)]}
end
end
How to test article creation without users association later in such way:
it "is not a valid article without users" do
article = build(:article, users: []) #doesn't work
expect(article.valid?).to eq(false)
end
You may be better off creating a sub-factory:
FactoryGirl.define do
factory :article do |f|
body "Some awesome text"
factory :article_with_users do
after(:build) {|article| article.users = [create(:user)]}
end
end
end
Then test by:
it "don't create article without users associations" do
article = build(:article) #doesn't work
expect(article.valid?).to eq(false)
end
it "do create article with users associations" do
article = build(:article_with_users)
expect(article.valid?).to eq(true)
end
This, however, depends on your validation setup in your models as well.
it don't, it do :)

Resources