Multiple versions of the same factory (FactoryGirl) - ruby-on-rails

Is there a way to have multiple versions of the same factory? For example, a user factory.
FactoryGirl.define do
factory :user#1 do
name 'John Doe'
date_of_birth { 21.years.ago }
end
factory :user#2 do
name 'Jane Doe'
date_of_birth { 25.years.ago }
end
end
is there something like this, so that I could call FactoryGirl.create :user#1 for John or FactoryGirl.create :user#2 for Jane?
The user factory is an example I don't actually use but my real factory is using a lot of data. I find it cumbersome to manually change a lot of data everytime I need another user.

You do not have to declare two times
just follow
FactoryGirl.define do
factory :user do
name
date_of_bith
end
end
Now Dynamically you can call
user1 = FactoryGirl.create(:user, name: 'John doe', date_of_birth: 21.year.ago)
user2 = FactoryGirl.create(:user, name: 'Jane doe', date_of_birth: 25.year.ago)

As opposed to providing all the data in the call, I just make my factories dynamic by using sequences to give me unique data.
FactoryGirl.define do
factory :mail_user do
mail_domain
account
sequence :userid do |n|
"mailuser-#{n}"
end
passwd "This!sADecentPassword11212"
active :true
filter :false
end
end
or by using traits / inheritance to give me the exact user data I need
FactoryGirl.define do
factory :administrator do
isp
sequence :email do |n|
"user#{n}#example.com"
end
active true
name "Random Awesome Admin"
password '123Abc#$1'
password_confirmation '123Abc#$1'
trait :inactive do
active false
end
trait :system do
roles [:system_admin]
end
trait :admin do
roles [:admin ]
end
trait :realm do
roles [:realm_admin]
end
trait :helpdesk do
roles [ :helpdesk ]
end
trait :dns do
roles [ :dns_admin ]
end
factory :inactive_administrator, traits: [:inactive]
factory :helpdesk_admin, traits: [:helpdesk]
factory :system_admin, traits: [:system]
factory :admin, traits: [:admin]
factory :realm_admin, traits: [:realm]
factory :dns_admin, traits: [:dns]
end
end
Since in most cases I don't care about the actual username, or the like. just that it passes validation. So I would look at sequences/traits/dependent attributes. So that you rely less on hardcoded data.

You can generate dynamic factories by using sequence
factory :user do
sequence(:name) { |n| "person#{n}" }
sequence(:date_of_birth) { |n| "#{n}".years.ago }
end
And if you want to generate birth date based on your validation, your can override the initial value used for sequence
sequence(:date_of_birth, 15) { |n| "#{n}".years.ago }

Related

With Rails 6.2 how do I detect if I've passed an empty array into my factory?

I’m using Rails 6.2. I have a user factory (FactoryBot 6.2) that I set up like so
FactoryBot.define do
factory :user do
…
after(:build) do |user, vars|
…
if vars.addresses.nil?
user.addresses = build_list(:address, 1, user: user)
end
end
My user model has
has_many :addresses
And my address model has
belongs_to :user
The problem with all of the above is that these calls behave the same way
create(:user, addresses: [])
And
create(:user)
In the first case, I want an empty array to be assigned to the user object. In the second case, I would like the addresses auto-created using my address factory. How do I distinguish between passing in an empty array and not passing in anything at all?
I tend to use transients for this sort of thing to make it clear what I'm trying to do.
So something like:
FactoryBot.define do
factory :user do
transient do
with_addresses { true }
end
...
after(:build) do |user, vars|
if vars.with_addresses == true
user.addresses = build_list(:address, 1, user: user)
elsif vars.with_addresses
user.addresses = vars.with_addresses
end
end
end
end
and then:
create(:user) # Builds one address for the user
create(:user, with_addresses: []) # Starts with no addresses
create(:user, with_addresses: [a1, a2]) # Starts with two addresses
And you could make it a little smarter and more flexible so that you could say things like:
create(:user, with_addresses: 0) # To create with none
create(:user, with_addresses: 6) # To get 6 from your address factory
You'd just need a couple small tweaks to the after(:build) block.

Rails Factory Girl: Create record associated with user but with role

I have the following factory girl for create user:
FactoryGirl.define do
factory :user do
first_name 'Colin'
last_name 'Ambler'
password 'dell13a1'
address_zip '60657'
trait :event_operator do
role Role.find_or_create_by(role_title: "Event Operator", name: "event_operator")
email "cash#cash.com"
end
trait :athletic_trainer do
role Role.find_or_create_by(role_title: "Athletic Trainer", name: "athletic_trainer")
email "cash1#cash.com"
end
end
end
As you can see I use the traits for define the role of the user, and set up a different email address.
I have another model called Event, an event belongs to a athletic_trainer that's the name of the association, it just points to the User model, so I use Factory Girl for create the event like this:
FactoryGirl.define do
factory :event do
event_type Event.event_types[:tournament]
sport Event.sports[:lacrosse]
event_title "NXT Cup"
event_logo { Rack::Test::UploadedFile.new(File.join(Rails.root, 'spec', 'support', 'images', 'nxt_cup_logo.png')) }
gender Event.genders[:boys]
event_pay_rate 40
event_participant_numbers 3
event_feature_photo { Rack::Test::UploadedFile.new(File.join(Rails.root, 'spec', 'support', 'images', 'nxt_cup_feature.jpg')) }
event_description "We roll out the red carpet for North America's top youth talent. The nations top youth teams descend on the premier facilities in the Philadelphia region to battle it out and stake claim to one of the summers most highly coveted trophies...the NXT Cup."
event_operator
end
end
as you can see I add the event_operator at the end of the event's factory girl for specify the user association, however this way I do not have access to the user associated in the factory girl.
is it possible to send the athletic_trainer as a parameter to the event's factory girl? I mean I want to create the user using factory girl and then create the event but associate the user I just created, is that possible?
You can always override fields by passing them to the build or create methods. To set event_operator to your desired user, you would do this:
user = build(:user)
event = build(:event, event_operator: user)

Rails FactoryGirl Sequences - Test created object

I use sequences like the following
sequence (:role_id){|n| "10#{n}" }
Now when i create a factory with
FactoryGirl.create :account, name: "name", phone:"1234", role_id: FactoryGirl.generate(:role_id)
How can i test for the generated role_id value?
expect(account.role_id).to eq ?
You shouldn't do it. Sequences are used to generate unique random data.
If you want to have a role association on your model, consider something like this in your factory:
FactoryGirl.define do
factory :person do
role
sequence(:email) { |n| "email-#{n}#example.com" }
phone SecureRandom.uuid
end
end
I'm not sure I follow how you use sequencing.
Traditional way is in your Factory
FactoryGirl.define do
factory :account do
name { "whatever" }
sequence(:role_id){ |i| "10#{i}"}
phone{ "5555" }
end
end
You can then generate accounts like so:
account = FactoryGirl.create :account, name: "name", phone:"1234"
it will override every passed parameter with what you provided.
You can also specify :role_id:
account = FactoryGirl.create :account, name: "name", phone:"1234", :role_id => "1015"
Now, you can test any value you wish. For instance with RSpec:
account = FactoryGirl.create :account, name: "name", phone:"1234"
expect(account.role_id).to eq "1001".
But there is no point since what you are testing here is FactoryGirl. And that has already been tested. :)

How should I set up complex Factories in Rails?

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?

Factory Girl: Creating associated records

I'm trying to do something I think should be fairly simple with Factory Girl and can't quite get it. A user has many stories and I'm testing viewing a user's profile page, which lists their created stories.
I looked up creating associated factories and the docs say I can do something like this:
FactoryGirl.define do
factory :story do
title "My Story"
segments_limit 5
beginning "Once upon a time"
completion_status false
user
end
factory :user do
sequence(:username) { |n| "user-#{n}" }
sequence(:email) { |n| "user-#{n}#example.com" }
password "password"
password_confirmation "password"
factory :user_with_stories do
ignore do
stories_count 5
end
after(:create) do |user, evaluator|
create_list(:story, evaluator.stories_count, user: user)
end
end
end
end
This doesn't seem to work though - when I get into the console and run FactoryGirl.create(:user_with_stories).stories.length, I get an empty array. Am I missing something?
you should use after :build and build_list and assign the list to the user's association, like so:
after(:build) do |user, evaluator|
user.stories << build_list(:story, evaluator.stories_count, user: user)
end
Stories are added after creation of user, you need to instantiate the user after creation to see stories attached :
user = FactoryGirl.create(:user_with_stories)
user = User.find user
user.stories.length

Resources