This blog post goes about a common problem I’ve haven’t found much documentation on.
It follows issues mentioned of multiple places like here and variations of it.
I’ve written some code to illustrate the problem on my Github FactoryGirl-Problem to accompany this.
How to create factories such that the association is a 1-1, and all models can be created independently.
# app/models/user.rb
class User < ActiveRecord::Base
has_one :profile
end
# app/models/profile.rb
class Profile < ActiveRecord::Base
belongs_to :user
validates :user,
presence: true,
uniqueness: true
end
How to create the factories to get the following tests to pass
# spec/models/user_spec.rb
describe User do
describe 'creating' do
subject { create(:user) }
it 'can be done' do
expect(subject).not_to be_nil
end
it 'has the profile association' do
expect(subject.profile).not_to be_nil
end
end
end
# spec/models/profile_spec.rb
describe Profile do
describe 'creating' do
subject { create(:profile) }
it 'can be done' do
expect(subject).not_to be_nil
end
it 'has the user association' do
expect(subject.user).not_to be_nil
end
end
end
The circular dependency of associations make it hard to create.
# spec/factories/profiles.rb
FactoryGirl.define do
factory :profile do
user
end
end
# spec/factories/users.rb
FactoryGirl.define do
factory :user do
name 'MyString'
profile
end
end
This fails due to a circular dependency: user creates a profile, and profile creates a user.
Many tries were made including after(:build)
, after(:create)
hooks into factories amongst others.
After much attempt to find solution online, I managed to find the following to solve my issue
The solution I came up with after a lot of time is the following:
# spec/factories/profiles.rb
FactoryGirl.define do
factory :profile do
association :user, ignore_profile: true
end
end
# spec/factories/users.rb
FactoryGirl.define do
factory :user do
name 'MyString'
ignore do
ignore_profile false
end
after(:create) do |user, evaluator|
user.profile ||= build(:profile, user: user) unless evaluator.ignore_profile
end
end
end
This takes advantage of the trait to build a profile only after the user is created. For most cases, this should do the job.
I’d like to be able to change the user model to the following:
# app/models/user.rb
class User < ActiveRecord::Base
has_one :profile
validates :profile, presence: true
end
However, this fails tests as user cannot be crated.
I’d also like to create stubbed objects by adding the following to the tests suite
# app/spec/user_spec.rb
describe User do
describe 'stubbing' do
subject { build_stubbed(:user) }
it 'can be done' do
expect(subject).not_to be_nil
end
it 'has the profile association' do
expect(subject.profile).not_to be_nil
end
end
end
# app/spec/profile_spec.rb
describe Profile
describe 'stubbing' do
subject { build_stubbed(:profile) }
it 'can be done' do
expect(subject).not_to be_nil
end
it 'has the user association' do
expect(subject.user).not_to be_nil
end
end
end
When I find a solution somewhere, I will come and update this blogpost.
{% raw %}
In the meantime, I have a solution that does the job for most of the cases.