I have a factory that work's fine when its symbol is :notification_event but when I change the name to player_notification_event it fails with the error
uninitialized constant PlayerNotificationEvent.
Also, my other factory :property_notification_event is failing as well, with the error
uninitialized constant PropertyNotificationEvent.
failing factories
factory :player_notification_event do
notification_eventable_type 'Player'
association :notification_eventable, factory: :player
unread_count 1
last_notif_unread_count 0
last_email_message_count 0
last_email_time 5.hours.ago
last_notif_time 3.hours.ago
end
factory :property_notification_event do
notification_eventable_type 'Property'
association :notification_eventable, factory: :property
unread_count 1
last_notif_unread_count 0
last_email_message_count 0
last_email_time 5.hours.ago
last_notif_time 3.hours.ago
end
failing specs
let(:player_notification_event) { create :player_notification_event }
let(:property_notification_event) { create :property_notification_event }
it 'sends email to player' do
player = player_notification_event.notification_eventable
allow(UnreadMessagesMailer).to receive_message_chain(:player_email, :deliver_now!)
described_class.perform
expect(UnreadMessagesMailer).to have_received(:player_email)
end
it 'sends email to property' do
property = property_notification_event.notification_eventable
allow(UnreadMessagesMailer).to receive_message_chain(:property_email, :deliver_now!)
described_class.perform
expect(UnreadMessagesMailer).to have_received(:property_email)
end
passing spec
let(:player_notification_event) { create :notification_event }
it 'sends email to player' do
player = player_notification_event.notification_eventable
allow(UnreadMessagesMailer).to receive_message_chain(:player_email, :deliver_now!)
described_class.perform
expect(UnreadMessagesMailer).to have_received(:player_email)
end
passing factory
factory :notification_event do
notification_eventable_type 'Player'
association :notification_eventable, factory: :player
unread_count 1
last_notif_unread_count 0
last_email_message_count 0
last_email_time 5.hours.ago
last_notif_time 3.hours.ago
end
You can use inheritance here instead of duplicating the entire factory.
[...] it's good practice to define a basic factory for each class with only
the attributes required to create it. Then, create more specific
factories that inherit from this basic parent. Factory definitions are
still code, so keep them DRY.
https://www.rubydoc.info/gems/factory_bot/file/GETTING_STARTED.md
factory :notification_event do
unread_count 1
last_notif_unread_count 0
last_email_message_count 0
last_email_time 5.hours.ago
last_notif_time 3.hours.ago
factory :player_notification_event do
notification_eventable_type 'Player'
association :notification_eventable, factory: :player
end
factory :property_notification_event do
notification_eventable_type 'Property'
association :notification_eventable, factory: :property
end
end
Since the model class is derived from the parent factory :notification_event you don't need to manually specify it.
The default of factory_bot is to look for a class with the same name as first argument in factory, if you don't pass a class explicitly (check official guide). Try this:
factory :player_notification_event, class: NotificationEvent do ...
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
I am attempting to create my FactoryGirl factories such that when I call FactoryGirl.create(:model_a), any dependency for model_a are created and assigned to that model_a factory. However, for some reason my method is not working and I can't quite figure out why.
In my factory file this is what I have:
FactoryGirl.define do
factory :model_a do
before(:create) do
FactoryGirl.create(:model_b)
end
model_b {ModelB.first}
end
end
Now when I run FactoryGirl.create(:model_a) I would expect this to first create the factory model_b (because of the before(:create) call) and then go back to creating the factory model_a and assigning the factory model_b to the model_b relationshionship for model_a
But instead, I am getting the error model_b must exist, model_b cannot be blank.
Why is the factory model_b not being created so that I can use it?
You need to set the association between model_a and model_b inside your before(:create) block. For example:
FactoryGirl.define do
factory :model_a do
# add model_a attributes as needed
before(:create) do |model_a|
model_a.model_b = ModelB.first || FactoryGirl.create(:model_b)
end
end
end
Or, per OP's comment:
factory :model_a do
# add model_a attributes as needed
model_b { ModelB.first || FactoryGirl.create(:model_b) }
end
I'm trying to create instance with has_many relationship using FactoryGirl factories and fail.
I have two classes: Computers and NetworkCards. Each Computer can have many NetworkCards.
Defined two ActiveRecords:
1.
class Computer < ActiveRecord::Base
has_many :network_cards
end
2.
class Article < ActiveRecord::Base
belongs_to: computer
end
Defined the following factories:
1.
factory :computer do
sequence(:name) { |n| "PC_#{n}" }
transient do
network_cards_count 1
end
after(:create) do |computer|
create_list(:network_card, evaluator.network_cards_count, computer: computer)
done
end
2.
factory :network_card do
sequence(:name) { |n| "NC_#{n}" }
sequence(:type) { |n| "TYPE_#{n}" }
end
Now, when creating a computer in a RSpec test I get a weird behavior I can't explain.
#computer_1 = FactoryGirl.create(:computer)
#computer_1.network_cards.size #Expect 1. Got 0. Why?
#computer_1.reload
#computer_1.network_cards.size # Got 1
Any ideas what am I missing?
Try this:
after(:build) do |computer, evaluator|
computer.network_cards << build_list(:network_card, evaluator.network_cards_count, computer: computer)
end
factory :computer do
sequence(:name) { |n| "PC_#{n}" }
transient do
network_cards_count 1
end
after(:create) do |computer, evaluator|
create_list(:network_card, evaluator.network_cards_count, computer: computer)
computer.reload #<----- solves the issue
end
end
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.
Hello, I'm trying to do test but I have some problems. First, file conn.rb:
FactoryGirl.define do
factory :conn, class: Connection do
from_id 1
to_id 4
end
end
Second file, leg.rb:
FactoryGirl.define do
factory :leg_le, class: Leg do
association :connection_id, factory: :conn
from_id 1
to_id 2
end
end
And my goal is to create leg_le with ID of conn:
conn=FactoryGirl.create(:conn)
leg=FactoryGirl.create(:leg_le)
It seems to create but connection_id is nil.
You may try :
FactoryGirl.define do
factory :connection do
from_id 1
to_id 4
end
end
FactoryGirl.define do
factory :leg do
association :connection, factory: :connection
from_id 1
to_id 2
end
end
then
FactoryGirl.create(:leg).connection_id
# => 1 or whatever your connection_id is
or (if you want to reuse the connection instance elsewhere) :
connection = FactoryGirl.create(:connection)
FactoryGirl.create(:leg, :connection=> connection).connection_id
# => 1 or whatever your connection_id is