I have the following factory for patient_allergies
FactoryGirl.define do
factory :patient_allergy do
patient
name 'Peanuts'
end
end
The following factory for patient_allergy_reactions
FactoryGirl.define do
factory :patient_allergy_reaction do
patient_allergy
name 'Fever'
severity 'High'
end
end
The model for patient_allergy looks like this:
class PatientAllergy < ActiveRecord::Base
belongs_to :patient
has_many :patient_allergy_reactions
end
the model for patient_allergy_reaction looks like this:
class PatientAllergyReaction < ActiveRecord::Base
belongs_to :patient_allergy
end
My model tests look this:
it 'returns correct allergies with reactions' do
#create an allergy
allergy_name = 'Peanuts'
patient_allergy = create(:patient_allergy, name: allergy_name, patient: patient)
#create a allergy reaction
reaction_name = 'Fever'
reaction_severity = 'Low'
allergy_reaction = create(:patient_allergy_reaction, name: reaction_name, severity: reaction_severity, patient_allergy: patient_allergy)
expect(patient.patient_allergies.size).to eq(1)
expect(patient.patient_allergies[0]).to eq(patient_allergy)
expect(patient.patient_allergies[0].patient_allergy_reactions[0]).to eq(allergy_reaction)
end
The above works fine but doesnt seem to add much value.
I am trying to figure out a way to use build and traits for the above test.
Else, is there a way to use the expect(patient).to have_many(:patient_allergies) matcher or something.
It would be really helpful if i could understand testing my models with factory girl.
The above works fine but doesnt seem to add much value
Agreed. Your model specs should test methods that you write, instead of testing the behavior of Rails.
If you want to test your associations, you can check out shoulda-matchers, which has standard tests for Rails models.
Related
I’m using Rails 6 with FactoryGirl 4.9.0. I have these models, one has a has_many with the other …
class Contract < ApplicationRecord
…
belongs_to :client
class Client < ApplicationRecord
…
has_many :contracts
In my factory, I have
FactoryGirl.define do
factory :client do
name "Test Client”
end
factory :dp_client, class: Client do
Contracts { [create(:contract)] }
end
end
But when I go to use the factory,
create(:dp_client)
I get the error …
Validation failed: Client must exist
When creating the "has_many" relationship in jn my factory, I do I tie it back to the object that's creating it?
FactoryBot.define do
factory :client do
name { "Test client" }
end
factory :contract do
client
end
factory :dp_client, class: Client do
name { "client name" }
after(:create) do |client|
create(:contract, client: client)
end
end
end
It's done like this:
FactoryBot.define do
factory :client do
name { "Test client" }
end
factory :contract do
association :client
end
factory :dp_client, class: Client do
name { "Test DP Client"}
contracts { [ FactoryBot.create(:contract) ]}
end
end
There's multiple ways to deal with this.
Option 1:
Use the instance in your block. FactoryBot has self set to the factory and also passes it in as an argument. So both of these should work:
contracts { |factory| [build(:contract, client: factory.instance)] }
# or
contracts { [build(:contract, client: instance)] } # because self is the factory
Note: I use build here instead of create because of how Rails autosaves associations. Both should work fine in most use cases.
Option 2: Use after_create
This is the solution that was proposed by Saiqul Haq. Main downside: It exclusively works for create (not for build). Also noteworthy: It might not reflect the reality of your app (e.g. if your records are always created together).
Option 3: Use before_create
Similar to the previous solution, but potentially more realistic in terms of workflow:
before(:create) do |client|
client.build_contract(attributes_for(:contract))
end
What is the correct way to assign associations that already exist?
I am trying to assign a has_one relationship between a user and a city, where the same city can be used by many users or other entities (e.g. an event).
Code
FactoryGirl.define do
factory :user do
name 'john'
trait :in_boston do
association :city, factory: :boston
end
end
end
Error
PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "city_pkey" because it's trying to create Boston twice in the database.
What I would like to do is simply reference the existing factory, not create a new one.
My current working (but less than ideal) solution
FactoryGirl.define do
factory :user do
name 'john'
trait :in_boston do
after(:create) do |user|
user.city = City.find_by_name('Boston') || create(:boston)
end
end
end
end
Any guidance would be appreciated, thanks.
So, I'm going to assume that your model code is golden, and show you how I'd setup the test. I'm not sure why you need the factory to have decision making powers based on if the city exists or not. Just instantiate the city in its own factory and call the association in your test setup.
Factories
# factories/cities.rb
FactoryGirl.define do
factory :city do
name 'Boston'
end
end
# factories/users.rb
FactoryGirl.define do
factory :user do
name 'john'
city
end
end
Test
describe 'blah' do
let( :city ){ create :city }
let( :user ){ create :user, city: city }
it 'user should have a city' do
expect( user.city.name ).to eq 'Boston'
end
end
I had the same issue when testing a model that belonged to another model, when a callback was creating that association.
To explain simply, let's say I have a Book model, and a Page model, with Page belongs_to Book, and a callback to create Page when a book is created.
In my factory for Page, I try to associate to Book, but by doing so I create book once, and the creation of the page itself create the same book again. By UniqueIndex condition, PostgreSQL fails.
The simplest solution in that case is to not to create Page when testing the Page model, but instead to simply create(:book) and then use book.page.
Assume I have the following Rails models and the method shown is tested.
class Employee < ActiveRecord::Base
has_many :jobs
def total_annual_income
jobs.collect { |j| j.annual_salary}.sum
# Or some other AR magic to do it directly in the database; doesn't matter
end
end
class Job < ActiveRecord::Base
# property :annual_salary
belongs_to :employee
end
Now, assume that I'm going to write some other method elsewhere that calls Employee#total_annual_income. When I test this method with FactoryGirl, is it possible to set up my Employee factories directly with a total_annual_income property without having to make corresponding Job factories? I.e., can I simply do
FactoryGirl.define do
factory :employee1, class: Employee do
id 100
total_annual_income 100000.0
end
end
instead of
FactoryGirl.define do
factory :employee1, class: Employee do
id 100
end
end
# WANT TO OMIT THIS ENTIRE SET OF FACTORIES #
FactoryGirl.define do
factory :employee1_job1, class: Job do
id 100
employee_id 100
annual_salary 60000.0
end
factory :employee1_job2, class: Job do
id 101
employee_id 100
annual_salary 40000.0
end
end
# WANT TO OMIT THIS ENTIRE SET OF FACTORIES #
I'm kinda new to FactoryGirl still, so apologies if I've overlooked something basic.
Have a look at the Associations information under the Factory Girl documentation:
https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md#associations
This has an example of :user_with_posts which uses #create_list to generate a list of posts for a user - a bit like your list of jobs. Since on StackOverflow it is customary to include full answers in case an external link should become broken, here's copypasta of the example with its comments:
Generating data for a has_many relationship is a bit more involved, depending on the amount of flexibility desired, but here's a surefire example of generating associated data.
FactoryGirl.define do
# post factory with a `belongs_to` association for the user
factory :post do
title "Through the Looking Glass"
user
end
# user factory without associated posts
factory :user do
name "John Doe"
# user_with_posts will create post data after the user has been created
factory :user_with_posts do
# posts_count is declared as a transient attribute and available in
# attributes on the factory, as well as the callback via the evaluator
transient do
posts_count 5
end
# the after(:create) yields two values; the user instance itself and the
# evaluator, which stores all values from the factory, including transient
# attributes; `create_list`'s second argument is the number of records
# to create and we make sure the user is associated properly to the post
after(:create) do |user, evaluator|
create_list(:post, evaluator.posts_count, user: user)
end
end
end
end
This allows us to do:
create(:user).posts.length # 0
create(:user_with_posts).posts.length # 5
create(:user_with_posts, posts_count: 15).posts.length # 15
The core of this is really that #create_list method shown above.
[EDIT] Completely untested, I think your example becomes something like:
FactoryGirl.define do
factory :employee_with_jobs, class: Employee do
id 100
transient do
jobs_count 2
end
after(:create) do |employee, evaluator|
create_list(:job, evaluator.jobs_count,
employee: employee,
annual_salary: 40000.0)
end
end
end
create(:employee_with_jobs, jobs_count: 5) # Expecting total salary 200000.0.
...more or less.
I can't seem to get FactoryGirl to call my before_add callback with an associated model.
I've got a Course model with:
has_many :lessons, dependent: :destroy, before_add: :set_lesson_number
def set_lesson_number
#stuff
end
a Lesson model with:
belongs_to :course.
a Lesson factory with:
FactoryGirl.define do
factory :lesson do
course
end
end
and a Course factory, defined as suggested by the Factory Girl wiki:
FactoryGirl.define do
factory :course do
factory :course_with_lessons do
transient do
lessons_count 10
end
after(:create) do |course, evaluator|
create_list(:lesson, evaluator.lessons_count, course: course)
end
end
end
end
The before_add callback doesn't get called when I do FactoryGirl.create(:lesson), but it does get called if I do this:
lesson = FactoryGirl.build(:lesson)
course = lesson.course
course.lessons << l
In both cases, course.lessons ends up including lesson.
By the same token, FactoryGirl.build(:course_with_lessons) doesn't work with the above Course factory, but if I replace the create_list line with:
evaluator.lessons_count.times do
course.lessons << build(lesson)
end
it does. It seems like FactoryGirl is creating the Lessons and setting their Course ID's, but somehow not actually "adding" them to the collection, so I have to do it manually.
Am I missing something about how FactoryGirl is supposed to work? Or about how ActiveRecord works?
This is how ActiveRecord works.
If you run the following in your rails console you'll see the before_add callback on the association is not called:
course = Course.create
Lesson.create(course_id: course.id)
I imagine FactoryGirl.create_list generates objects in a similar way.
The lesson needs to be added to the collection in order for the callback to fire. This can be done in a couple of ways.
1. Create a Lesson through course
course.lessons.create
2. Explicitly add the lesson to course.lessons
course.lessons << lesson
3. Explicitly add a collection of lesson to course.lessons
course.lessons = [lesson1, lesson2]
To get the callback to fire, you could modify your factory like so:
factory :course do
factory :course_with_lessons do
transient do
lessons_count 10
end
after(:create) do |course, evaluator|
course.lessons =
build_list(:lesson, evaluator.lessons_count, course: course)
course.save
end
end
end
Hope that helps.
I'm using Factory Girl, and I want to specify a belongs_to association that uses a trait. This code (without traits) works:
FactoryGirl.define do
factory :challenge_participation do
user
challenge
program
end
end
But how would I add the with_days trait to my program? (The with_days trait sets up some has_many associations).
I imagine it'd be something like this, but I can't find the right syntax:
FactoryGirl.define do
factory :challenge_participation do
user
challenge
program :with_days # THIS DOESN'T WORK
end
end
Ah, I'm an idiot. Figured it out, the program method accepts a block. So, you do it like this:
FactoryGirl.define do
factory :challenge_participation do
user
challenge
program { FactoryGirl.create(:program, :with_days) }
end
end