I have two factories for my Company and Employee models respectively. Employee is in a belongs_to relationship with Company. Here's my two factories:
factory :company do
name "foo company"
end
factory :employee do
company
name 'Willy Bytes'
end
There are some occasions where I need to traverse an array of data and populate new Employee records accordingly to test against different conditions in my specs. To illustrate, I'm using the following specs to test one of my inclusion validations:
it "should be valid if they like red blue or green" do
["red","blue","green"].each do |c|
FactoryGirl.build(:employee, :favourite_colour => c).should be_valid
end
end
it { FactoryGirl.build(:upload, :favourite_colour => "other").should_not be_valid }
However, I have a uniqueness constraint on the parent companies name field which yields an error when I attempt to build the record. Is there an intelligent way to resolve/avoid this problem? I run into these types of specs a lot and typically what I would do is define a single Company factory and assign that to each record in the loop, but it doesn't feel intuitive and results in a lot of repetition. Is a sequence the only other way around this?
You can use factory_girl's sequence generator. Your factory will look like this.
factory :company do
sequence(:name) {|n| "company #{n}"}
end
One line:
sequence(:name) { |n| "Company #{n}" }
I found exactly what I was looking for here: Find or create record through factory_girl association
Overriding the default build / new behaviour using the initialize_with method seems to be the way to go:
factory :company do
initialize_with { Company.find_or_create_by_name("Foo Company") }
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)
My app has a Phone model, and one of the fields is .guid which is set with before_create :set_guid (which generates a random string and puts it in the guid field when the model is created).
My factory for phone looks like:
require 'faker'
FactoryGirl.define do
factory :phone do |f|
f.phone { Faker::PhoneNumber.phone_number }
end
end
This simple test to create a phone fails my model's validation because the guid is blank:
describe Phone do
it "has a valid factory" do
FactoryGirl.create(:phone).should be_valid
end
I could of course manually stuff the guid field in the Factory definition, but isn't the point of the Factory to run the model's normal validations and callbacks to ensure they are working?
Clearly I am missing something - what IS the right way to use FactoryGirl to create instance of a model that properly exercises before_create callbacks that geenrate guids etc.?
You have a model Phone with an attribute phone?
All that aside, there is factory girl callbacks in their Getting Started.
factory :user do
callback(:after_stub, :before_create) { do_something }
after(:stub, :create) { do_something_else }
before(:create, :custom) { do_a_third_thing }
end
Something like the before(:create) may work for you.
Can't you just hard code a guid in the default Factory as well? i mean you just want to test it once to see if it works, and then the rest is just ballast, cause you want working tests.
I am using Ruby on Rails 3.0.9, RSpec-rails 2 and FactoryGirl. I am trying to state a Factory association model but I am in trouble.
I have a factories/user.rb file like the following:
FactoryGirl.define do
factory :user, :class => User do
attribute_1
attribute_2
...
association :account, :factory => :users_account, :method => :build, :email => 'foo#bar.com'
end
end
and a factories/users/account.rb file like the following:
FactoryGirl.define do
factory :users_account, :class => Users::Account do
sequence(:email) {|n| "foo#{n}#bar.com" }
...
end
end
The above example works as expected in my spec files, but if in the factory :users_account statement I add the association :user code so to have
FactoryGirl.define do
factory :users_account, :class => Users::Account do
sequence(:email) {|n| "foo#{n}#bar.com" }
...
association :user
end
end
I get the following error:
Failure/Error: Unable to find matching line from backtrace
SystemStackError:
stack level too deep
How can I solve that problem so to access associated models from both sides\factories (that is, in my spec files I would like to use RoR association model methods like user.account and account.user)?
P.S.: I read the Factory Girl and has_one question and my case is very close to the case explained in the linked question. That is, I have an has_one association too (between User and Users::Account classes).
According to the docs, you can't just put both sides of the associations into the factories. You'll need to use their after callback to set an object(s) to return.
For instance, in the factories/users/account.rb file, you put something like
after(:build) do |user_account, evaluator|
user_account.user = FactoryGirl.build(:user, :account=>user_account)
end
For has_many associations, you'll need to use their *_list functions.
after(:build) do |user_account, evaluator|
user_account.users = FactoryGirl.build_list(:user, 5, :account=>user_account)
end
Note: I believe the example in the docs is a bit misleading it doesn't assign anything to the object. I believe it should be something like (note the assignment).
# the after(:create) yields two values; the user instance itself and the
# evaluator, which stores all values from the factory, including ignored
# 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|
user.posts = FactoryGirl.create_list(:post, evaluator.posts_count, user: user)
end
Spyle's excellent answer (still working with Rails 5.2 and RSpec 3.8) will work for most associations. I had a use case where a factory needed to use 2 different factories (or different traits) for a single has_many association (ie. for a scope type method).
What I ended up coming up with was:
# To build user with posts of category == 'Special' and category == 'Regular'
after(:create) do |user, evaluator|
array = []
array.push(FactoryBot.create_list(:post, 1, category: 'Regular')
array.push(FactoryBot.create_list(:post, 1, category: 'Special')
user.posts = array.flatten
end
This allowed the user to have 1 post of category 'Regular' and 1 post of category 'Special.'
I have written my basic models and defined their associations as well as the migrations to create the associated tables.
EDIT - Adding emphasis to what I specifically want to test.
I want to be able to test:
The associations are configured as intended
The table structures support the associations properly
I've written FG factories for all of my models in anticipation of having a complete set of test data but I can't grasp how to write a spec to test both belongs_to and has_many associations.
For example, given an Organization that has_many Users I want to be able to test that my sample Organization has a reference to my sample User.
Organization_Factory.rb:
Factory.define :boardofrec, :class => 'Organization' do |o|
o.name 'Board of Recreation'
o.address '115 Main Street'
o.city 'Smallville'
o.state 'New Jersey'
o.zip '01929'
end
Factory.define :boardofrec_with_users, :parent => :boardofrec do |o|
o.after_create do |org|
org.users = [Factory.create(:johnny, :organization => org)]
end
end
User_Factory.rb:
Factory.define :johnny, :class => 'User' do |u|
u.name 'Johnny B. Badd'
u.email 'jbadd#gmail.com'
u.password 'password'
u.org_admin true
u.site_admin false
u.association :organization, :factory => :boardofrec
end
Organization_spec.rb:
...
it "should have the user Johnny B. Badd" do
boardofrec_with_users = Factory.create(:boardofrec_with_users)
boardofrec_with_users.users.should include(Factory.create(:johnny))
end
...
This example fails because the Organization.users list and the comparison User :johnny are separate instances of the same Factory.
I realize this doesn't follow the BDD ideas behind what these plugins (FG, rspec) seemed to be geared for but seeing as this is my first rails application I'm uncomfortable moving forward without knowing that I've configured my associations and table structures properly.
Your user factory already creates an organization by virtue of the Factory Girl association method:
it "should associate a user with an organization" do
user = Factory.create(:johnny)
user.organization.name.should == 'Board of Recreation'
organization = user.organization
organization.users.count.should == 1
end
Take a look at 'log/test.log' after running your spec -- you should see an INSERT for both the organization and the user.
If you wanted to test this without the Factory Girl association, make a factory that just creates the user and make the association in the spec:
it "should associate a user with an organization" do
user = Factory.create(:johnny_no_org)
org = Factory.create(:boardofrec)
org.users.should be_empty
org.users << user
org.users.should include(user)
end
Of course all this is doing is testing whether ActiveRecord is doing its job. Since ActiveRecord is already thoroughly tested, you'll want to concentrate on testing the functionality of your application, once you've convinced yourself that the framework actually does what it's supposed to do.