FactoryGirl: specifying attributes on associated model - ruby-on-rails

I have two simple factories: One for user and one for blog. When creating a blog, it must have an associated user, so this is reflected in the blog factory:
FactoryGirl.define do
factory :user do
end
end
FactoryGirl.define do
factory :blog do
user #specifying the required association
end
end
Now I can create a blog in a spec like so:
blog = create(:blog)
what I am not sure of how to do in the spec is how to specify some custom attributes on the associated user record immediately when the blog record is first created.
For example: when creating the blog record (which also creates an associated user record), I want to specify the first_name attribute on that associated user record. Something like:
blog = create(:blog, associated_user: (first_name: 'foobar) )
That syntax up above doesn't work but I want to do something like that. I did look at the factory girl docs, and I have been googling but have been unable to figure this one out.
Question: How do I set some custom attributes on an associated object when creating an object with factory girl?

You can simply set the corresponding call argument with the needed Object.
user = create(:user, first_name: "Harald")
blog = create(:blog, user: user)
# or inline if preferred
blog = create(:blog, user: create(:user, first_name: "Heinz"))
Note: Don't forget to rather use build than create wherever possible to speedup your tests :)

Related

Build in-test associations using Factory_girl

I want to be able to create associations with ID only in each specific test, trying to avoid defining them in the factory.
I've been following Rails 4 Test Prescriptions
Avoid defining associations automatically in factory_girl definitions.
Set them test by test, as needed. You’ll wind up with more manageable
test data.
class Workspace < ActiveRecord::Base
has_many :projects
end
class Project < ActiveRecord::Base
belongs_to :workspace
end
This is what I want
test "" do
project_with_id = build_stubbed(:project)
workspace_with_id = build_stubbed(:workspace)
workspace_with_id.projects.push(project_with_id)
end
I am using build_stubbed to create valid ID's, which gives the following error:
*** RuntimeError Exception: stubbed models are not allowed to access the database - Project#save({:validate=>true})
So, reading factory girl's documentation I came up with working associations, but I don't want to define them in the factory, not even with traits.
FactoryGirl.define do
factory :project do
association :workspace, strategy: :build_stubbed
end
end
test "" do
project = build_stubbed(:project)
end
This works because I can call project.workspace, and both have a valid ID
How can I create valid associations (with ID), but without touching the database, only using Factory girl to create independent objects?
You could do something like this if you are using Rspec
let!(:user1) { FactoryGirl.create(:user) }
let!(:user_social_profile1) { FactoryGirl.create(:user_social_profile, user_id: user1.id) }
also in Rspec
let!(:user1) { FactoryGirl.create(:user) }
let!(:user_social_profile1) { FactoryGirl.create(:user_social_profile, user: user1) }
In minitest/test_unit I believe
user1 = FactoryGirl.create(:user)
user_social_profile1 = FactoryGirl.create(:user_social_profile, user_id: user1.id)
I am sorry I did not explain the issues associated with using build_stubbed with factory associations, this answer does a really good job at explaining that.
build_stubbed initializes a record and fakes persistence. So what you get is a record that answers true to persisted? and has an faked ID.
Its quite useful but the approach does not work when it comes associations as you would need to stub out large parts of ActiveRecord.
Instead you want to use create:
before do
#project = create(:project)
#workshop = create(:workshop, project: #project)
end
As far as I understand, you don't need an inverse to work, e.g. you'd be satisfied with workspace.projects being empty, wouldn't you?
This should work for you then:
workspace = build_stubbed(:workspace)
project = build_stubbed(:project, workspace: workspace)
If you need workspace.projects, you may use this:
project = build_stubbed(:project)
workspace = build_stubbed(:workspace, projects: [project])
project.workspace = workspace

How to create an association in FactoryGirl with a value lazy-evaluated from another FactoryGirl association

Is it possible to use an association as the value for another association in a trait?
Let me describe a (simplified) example. I have 3 models
User
Forum (belongs_to :forum)
Thread (belongs_to :forum, belongs_to :user)
Please note that Thread contains an user_id, it does not belong to User through Forum (at the time being, I can't change such constraint).
What I'd like to know, is if there a way to define a trait like the one
FactoryGirl.define do
factory :thread do
trait :with_forum do
association :user
# Lazy-evaluate the value of user to the previously created factory
# THIS IS THE KEY POINT: I want 'user' to be the factory created at the previous line
association :forum, user: user
end
end
end
What the trait is supposed to do is to create an user and associate it to the thread.
Then it should create a forum, but the user should be the same instance previously created.
There are mainly two reasons:
The user associated with the forum is the same of the thread
I don't want to create two user factories on cascade
Any idea? I tried to use the lazy evaluation, but no way to use it with associations.
Does it make sense to do the opposite and set the thread's user to be the same as the forum's? Does the forum factory create a user?
FactoryGirl.define do
factory :thread do
trait :with_forum do
forum
user { forum.user }
end
end
end
If you really want to do it the other way, you can definitely use a lazy attribute:
FactoryGirl.define do
factory :thread do
trait :with_forum do
user
forum { create(:forum, user: user) }
end
end
end
The downside there will be that it will always create a forum regardless of the strategy used to build the thread.
You have to lazily evaluate the association call:
FactoryGirl.define do
factory :thread do
trait :with_forum do
user
forum { association(:forum, user: user) }
end
end
end

How can I create a FactoryGirl factory for a Product that belongs_to a User?

I have read the FactoryGirl documentation, but I can't seem to figure this out. I know that it must be simple.
Here is my setup:
Rails 3.2.6
User has_many Products
FactoryGirl.define do
factory :product do
sequence(:identifier, 1000) {|n| "ABC#{n}" }
end
end
This fails because there must be a user_id associated with the Product. In my tests, I am creating a user during login. So my tests have access to the user object. I just don't know how to pass the user object in to the Product factory using FactoryGirl.
Assuming you already have a User factory (i.e factory :user do) you can add the following line in your :product factory:
association :user
That will automatically create a user factory (as defined) and assign it to your product.
When you create the object in your test though, you can still do something like:
FactoryGirl.create(:produce, user: some_user)
in order to assign a different user variable, because the Factory sees the association from the line you just added.
Hope this helps.

Getting two users from seeds.rb file when I only want one

If this is the only line I have creating a user in my seeds.rb file:
jim = Factory :user, :name => "Jim Jones", :email => 'jim#test.com'
How come I end up with two users, one named Jim, and one with a random name as if it was just generated by the factory?
Thanks!
John
UPDATE: The reason, I realize now, is because another factory is being created which has an association to user, and I don't specify which user, and so it would seem the factory with an association to user is generating a user.
I'm assuming you defined Factory :user somewhere? That's the general use case for Factory anyways. With that defined calling Factory :user will return whatever it was defined to do before. Have a look at the Getting Started With Factory_Girl page.
You should be able to call FactoryGirl.create(:user) from there.
Good luck!

Testing creation of associated objects on callback with Rspec on Rails

Trying to wrap my head around rspec and proper testing and having some hard time to do the following properly
Let's say we have three classes like
Class User
belongs_to :company
has_and_belongs_to_many :roles
end
Class Company
has_many :users
end
Class Role
has_and_belongs_to_many :users
end
In the User class I have before_create callback that assign the user default 'company_admin' role, if the user is first one to be associated with the company
def check_for_initial_company_admin_role
if self.company.users.count == 0
self.roles << Role.find_by_name("company_admin")
end
end
How do I properly test in my model spec that the user gets assigned the 'company_admin' role in case he's the first user associated with the company?
UPDATE
working solution
describe "#check_for_initial_company_admin_role" do
it "sets the first user created to be the administrator" do
Factory(:role)
user = Factory(:user)
user.roles.count.should be > 0
user.roles.should include Role.find_by_name("company_admin")
end
end
Factory.define :user do |f|
f.email { Factory.next(:email) }
f.password "secret"
f.password_confirmation "secret"
f.association :company
end
Factory.define :role do |f|
f.name "company_admin"
end
Factory.define :company do |f|
f.name "foobar"
f.vat_id "1234"
end
I would approach it like this:
describe "#check_for_initial_company_admin_role" do
it "sets the first user created to be the administrator" do
company = Factory(:company)
user = Factory(:user)
company.users << user
user.roles.count.should > 0
user.roles.should include Role.find_by_name("company_admin")
end
end
An assumption here that may be incorrect is that you are using Factory Girl in your test framework. If not, that doesn't really change the "meat" of this test...just those first lines where you create the company and user.
You could also optionally check the user from the company side of things but honestly that feels like a different test entirely--one testing the association between those models.
The approach I would take is that since this is actually a model test you need to create and alter a real model objects, rather than mocking out those objects. If this were a controller test, I'd mock the models and stub the models aggressively.
I hope this helps and addresses your question. If not, let me know where I'm off base and I'll make another pass at it :) I'm only about a year into rspec but I've found that once I wrapped my head around how to test models vs. controllers I've come to love it.
Without changing your existing logic, I would test this logic in the user_spec like so:
describe User do
let!(:admin_role) { Role.create!(name: 'company_admin') }
let!(:company) { Company.create! }
it 'should be added to the default role when created' do
user = company.users.create!(name: 'Joe', email: 'joe#email.com')
user.should have(1).roles
user.roles.first.should == admin_role
end
end
Note: I would be using something like FactoryGirl for the admin role and company objects to make them reusable.
Your use of the role name to indicate behavior is not ideal. It will likely lead to lots of scattered logic throughout your application where you are finding the role by its name and checking the name with if/else or case statements. I would recommend using Rail's single table inheritance and moving all the admin role logic to a separate class. It will keep the model's logic cleaner and make testing much easier.

Resources