There is the following code for creating objects:
FactoryGirl.define do
factory :user do
name 'Some name'
phone '89277777777'
address 'Some address'
end
factory :order do
title 'Some title'
end
end
User model has got has_many :orders association, and is it possible to pass user factory to order factory?
Thanks in advance.
Yes, as follows:
FactoryGirl.define do
factory :user do
name 'Some name'
phone '89277777777'
address 'Some address'
end
factory :order do
user
title 'Some title'
end
end
In your specs:
FactoryGirl.create(:order)
will create both an order object and an associated user object.
more information on FactoryGirl with Rails - https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md
Yes, the solution is as simple as using the association method
factory :order do
title 'Some title'
association :user
end
that can be shortened to
factory :order do
title 'Some title'
user
end
A word of warning. This approach tends to make your application very slow as long as the complexity of your models will increase.
In fact, if you have a purchase associated to an order associated to an user, etc, then creating a Purchase will create on cascade all the associated records even if you don't need them. Your tests will become slower over the time.
I suggest you to use the traits to compose your associations only when required.
factory :order do
title 'Some title'
trait :with_user do
user
end
end
FactoryGirl.create(:order, :with_user)
I normally also use :with_fake_user when I need the user id to be present for validation, but I'm testing something that doesn't require to access the user.
factory :order do
title 'Some title'
trait :with_user do
user
end
trait :with_fake_user do
user_id 0
end
end
FactoryGirl.create(:order, :with_user)
FactoryGirl.create(:order, :with_fake_user)
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.
How i can create object with association
I have Post model and PostsPhoto
PostsPhoto => belongs_to :post
Post => has_many :post_photos, dependent: :destroy,class_name: 'PostsPhoto'
I have tried
FactoryGirl.define do
factory :post do
article Faker::Lorem.paragraph(4)
video 'http://youtube.com'
author Faker::Name.name
category 'article'
title Faker::Name.title
post_photos
end
end
and have got
NoMethodError: undefined method `each' for #<PostsPhoto:0x007ff68e3bd698>
i cant use after(:create) {},because i validate it on create
def check_slider_photo
errors.add(:post_photos, 'Add post photos') if self.post_photos.size <= 0
end
I want to create(:post)=> returns me post object with post_photos
There are a couple ways around this. It depends on what you're testing exactly, but if you don't need to alter the objects or use them in queries, try using build_stubbed instead of create.
That is, if you'd typically say, FactoryGirl.create(:post), try instead saying FactoryGirl.build_stubbed(:post).
Another option would be not to run your validation in test and test that validation separately. To do this you can say
post = FactoryGirl.build(:post)
post.save(validate: false)
Then create the other objects and pass them post.id Happy testing.
Try this:
FactoryGirl.define do
factory :post do
article Faker::Lorem.paragraph(4)
video 'http://youtube.com'
author Faker::Name.name
category 'article'
title Faker::Name.title
factory :post_with_photos do
ignore do
photos_count 3
end
after(:build) do |post, evaluator|
post.post_photos << build_list(:post_photo, evaluator.photos_count)
end
end
end
end
In my rails application I have three objects I would like to model for a particular Rspec test. These are Organizations, Users, and Sales Opportunities.
class Organization
has_many :users
end
class User
has_many :sales_opportunities
end
An Organization has to have a unique name, and for the authorisation/authentication aspects of my app to work, both users need to belong to the same Organization. For the purposes of this test I want one User to be admin and one to be non-admin.
This works successfully in other parts of my application (e.g. sign up - the first user is automatically the admin, subsequent users are not, and for viewing links etc on pages). However what I now need to do is create two users (both belong_to one organization), make one of them the admin, and then when I delete the sales_opportunity non-admin user any sales_opportunities that belonged to this non-admin user should transfer ownership to the admin (for re-allocation).
FWIW I am using associations for some of the other tests - but in this case I can't create two users associated with one organization, so I've used:
before(:create) do |organization|
organization.users << FactoryGirl.build(:user ...admin params)
organization.users << FactoryGirl.build(:user ...non_admin params)
end
in the factory for the organization to build 2 users who both belong to one organization and have different characteristics. This works well.
If I try to add another before(:create) to the factory to then build a sales_opportunity it fails (because the users aren't created yet I believe). After(:create) statements also fall flat. I've also tried defining the non_admin user in the tests themselves (e.g. by using a let(:non_admin) = Organization.users.last and then non_admin.sales_opportunities.create statement), but this fails to product any sales opportunities (not sure what it does).
Are there any good resources on how to build tests like this? I can write the code to solve the problems in no time - I seem to waste a huge amount of time writing the tests first. I'm sure these are not particularly DRY either looking through my factories.
Any help would be appreciated.
in rspec/factories/sales_opportunities.rb:
FactoryGirl.define do
sequence(:name) { |n| "Sales Oppotunity - #{n}" }
factory :sales_opportunity do
user
name { generate(:name) }
# add more attributes here
end
end
in rspec/factories/users.rb:
FactoryGirl.define do
factory :user do
organization
sequence(:name) { |n| "Test user #{n}" }
sequence(:email) { |n| "test.user#{n}#example.com" }
password "password"
admin false
# add more attributes here
end
factory :admin_user, parent: :user do
admin true
end
factory :user_with_sales_opportunities, parent: :user, traits: [:with_sales_opportunity]
factory :admin_user_with_sales_opportunities, parent: :admin_user, traits: [:with_sales_opportunity]
trait :with_sales_opportunity do
ignore do # instead of ignore use transient if you're using factory girl 4.3 or above
sales_opportunities_count 5
end
after(:create) do |user, evaluator|
sales_opportunities { FactoryGirl.create_list(:sales_opportunity, evaluator.sales_opportunities_count, user: user) }
end
end
end
in rspec/factories/organizations.rb:
FactoryGirl.define do
sequence(:name) { |n| "Organization - #{n}" }
factory :organization do
name { generate(:name) }
ignore do
users_count 5
admin_users_count 2
sales_opportunities_count 5
end
after(:create) do |organization, evaluator|
users { FactoryGirl.create_list(:user_with_sales_opportunities, evaluator.users_count, organization: organization, sales_opportunities_count: sales_opportunities_count) + FactoryGirl.create_list(:admin_user_with_sales_opportunities, evaluator.users_count, organization: organization, sales_opportunities_count: sales_opportunities_count) }
end
# add more attributes here
end
end
Now you can:
let(:organization) { FactoryGirl.create(:organization, users_count: 4, admin_users_count: 3, sales_opportunities_count: 4) }
Im not sure exactly what you're trying to do, as you seem to be deleting the sales opportunity that you want to transfer?
If you think its an rspec you could instead try using after(:build)
Something like:
factory :user, class: User do
sequence(:email) { |n| "user#{n}#example.org" }
password "foobar"
# Standard user stuff etc
trait :admin do
after(:build) do |user|
user.set_admin
end
end
end
factory :organisation, class: Organisation do
name "Super Duper"
after(:build) do |organization|
organization.users << FactoryGirl.build(:user)
organization.users << FactoryGirl.build(:user, :admin)
end
end
I feel like you may have a modeling issue though. Shouldn't your sales opportunities be standalone?
ie:
class SalesOpportunity
field :name, type: String
field :phone, type: Integer
belongs_to :user
end
class User
field :name, type: String
field :phone, type: Integer
has_many :salesopportunities
end
This way you can delete a user object and persist the sale opportunity?
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.