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
Related
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.
I have a FactoryGirl for a model class. In this model, I defined some traits. In some traits, I don't want FactoryGirl callback calling but I don't know how. For example here is my code:
FactoryGirl.define do
factory :product do
sequence(:promotion_item_code) { |n| "promotion_item_code#{n}" }
after :create do |product|
FactoryGirl.create_list :product_details, 1, :product => product
end
trait :special_product do
# do some thing
# and don't want to run FactoryGirl callback
end
end
In this code, I don't want :special_product trait calls after :create. I don't know how to do this.
#Edit: the reason I want to this because sometimes I want generate data from parent -> children. But sometimes I want vice versa generate from children to parent. So When I go from children -> parent, callback at parent is called so children is created twice. That is not what I want.
#Edit 2: My question is prevent callback from FactoryGirl, not from ActiveRecord model.
Thanks
You can use transient attributes to achieve that.
Like:
factory :product do
transient do
create_products true
end
sequence(:promotion_item_code) { |n| "promotion_item_code#{n}" }
after :create do |product, evaluator|
FactoryGirl.create_list(:product_details, 1, :product => product) if evaluator.create_products
end
trait :special_product do
# do some thing
# and don't want to run FactoryGirl callback
end
end
But I think that a better way to model this problem is to define a trait for the "base case" or to have multiple factories.
You could use the same approach as described in the Factory Girl docs for a has_many relationship:
factory :product_detail do
product
#... other product_detail attributes
end
factory :product do
sequence(:promotion_item_code) { |n| "promotion_item_code#{n}" }
factory :product_with_details do
transient do
details_count 1 # to match your example.
end
after(:create) do |product, evaluator|
create_list(:product_detail, evaluator.details_count, product: product)
end
end
trait :special_product do
# do some thing
# and don't want to run FactoryGirl callback
end
end
This allows you to generate data for the parent->children:
create(:product_with_details) # creates a product with one detail.
create(:product_with_details, details_count: 5) # if you want more than 1 detail.
...and for the special product just
# does not create any product_details.
create(:product)
create(:product, :special_product)
To generate for children->parent
create(:product_detail)
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 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.
My problem seems very common, but I haven't found any answer in the documentation or the internet itself.
It might seem a clone of this question has_many while respecting build strategy in factory_girl but 2,5 years after that post factory_girl changed a lot.
I have a model with a has_many relation called photos. I want to populate this has many relation preserving my choice of build strategy.
If I call offering = FactoryGirl.build_stubbed :offering, :stay I expect offering.photos to be a collection of stubbed models.
The only way i've found to achieve this is this one:
factory :offering do
association :partner, factory: :named_partner
association :destination, factory: :geolocated_destination
trait :stay do
title "Hotel Gran Vía"
description "Great hotel in a great zone with great views"
offering_type 'stay'
price 65
rooms 70
stars 4
event_spaces 3
photos do
case #build_strategy
when FactoryGirl::Strategy::Create then [FactoryGirl.create(:hotel_photo)]
when FactoryGirl::Strategy::Build then [FactoryGirl.build(:hotel_photo)]
when FactoryGirl::Strategy::Stub then [FactoryGirl.build_stubbed(:hotel_photo)]
end
end
end
end
No need to say that IT MUST EXIST a better way of do that.
Ideas?
You can use the various FactoryGirl callbacks:
factory :offering do
association :partner, factory: :named_partner
association :destination, factory: :geolocated_destination
trait :stay do
title "Hotel Gran Vía"
description "Great hotel in a great zone with great views"
offering_type 'stay'
price 65
rooms 70
stars 4
event_spaces 3
after(:stub) do |offering|
offering.photos = [build_stubbed(:hotel_photo)]
end
after(:build) do |offering|
offering.photos = [build(:hotel_photo)]
end
after(:create) do |offering|
offering.photos = [create(:hotel_photo)]
end
end
end
Here's a slightly cleaner version of Flipstone's answer:
factory :offering do
trait :stay do
...
photos do
association :hotel_photo, :strategy => #build_strategy.class
end
end
end
You can also invoke the FactoryRunner class directly and pass it the build strategy to use.
factory :offering do
trait :stay do
...
photos do
FactoryGirl::FactoryRunner.new(:hotel_photo, #build_strategy.class, []).run
end
end
end
Other answers have a flaw, the inverse association is not being properly initialized, e.g. offering.photos.first.offering == offering is false. Even worse that being incorrect, the offering is a new Offering for each of the photos.
Also, explicitly specifying a strategy is redundant.
To overcome the flow and to simplify things:
factory :offering do
trait :stay do
...
photos do
association :hotel_photo, offering: #instance
end
end
end
#instance is an instance of the Offering being created by the factory at the moment. For the curious, context is FactoryGirl::Evaluator.
If you don't like the #instance like I do, you may look in evaluator.rb and find the following:
def method_missing(method_name, *args, &block)
if #instance.respond_to?(method_name)
#instance.send(method_name, *args, &block)
I really like how itself looks:
factory :offering do
trait :stay do
...
photos do
association :hotel_photo, offering: itself
end
end
end
Do be able to use itself, undefine it on the Evaluator:
FactoryGirl::Evaluator.class_eval { undef_method :itself }
It will be passed to the #instance and will return the #instance itself.
For the sake of providing a full example with several photos:
factory :offering do
trait :stay do
...
photos do
3.times.map do
association :hotel_photo, offering: itself
end
end
end
end
Usage:
offering = FactoryGirl.build_stubbed :offering, :stay
offering.photos.length # => 3
offering.photos.all? { |photo| photo.offering == offering } # => true
Be careful, some things might not work as expected:
offering.photos.first.offering_id will surprisingly be nil;
offering.photos.count will hit the database with a SELECT COUNT(*) FROM hotel_photos ... (and will return 0 in most cases), please use length or size in assertions.
This kind of thing works for me:
factory :offering do
trait :stay do
...
photos { |o| [o.association(:hotel_photo)] }
end
end