Rails FactoryGirl Sequences - Test created object - ruby-on-rails

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. :)

Related

FactoryGirl association duplicate key error

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.

Factory Girl self-join throwing validation errors

I'm trying to test a system for creating article translations where there is a self-join on the publications table. I've created a factory that will create multiple translations and associate them with a 'parent' article.
Using Rails 5 with factory_girl 4.7.0, rspec, and Database_cleaner
All actions work as expected, but creating a test is the problem
Here's the relevant model validations and methods:
# models/publication.rb
has_many :translations, class_name: "Publication", foreign_key: "translation_id", dependent: :nullify
belongs_to :translation, class_name: "Publication", optional: true
validates :language, uniqueness: { scope: :translation_id }, if: :is_translation?
def is_translation?
!translation.nil?
end
Factory (irrelevant code omitted):
# spec/factories/publication.rb
factory :publication, aliases: [:published_pub] do
title 'Default Title'
language 'EN'
published
after(:build) do |object|
create(:version, publication: object)
end
#-- This is where I suspect the problem stems from
trait :with_translations do
association :user, factory: :random_user
after(:build) do |object|
create_list(:translation, 3, {user: object.user, translation:object})
end
end
end
factory :translation, class: Publication do
sequence(:title) { |n| ['French Article', 'Spanish Article', 'German Article', 'Chinese Article'][n]}
sequence(:language) { |n| ['FR', 'ES', 'DE', 'CN'][n]}
user
end
And a basic test:
# spec/models/publication_spec.rb
before(:each) do
#translation_parent = create(:publication, :with_translations)
#pub_without_trans = create(:publication, :with_random_user)
end
scenario 'is_translation?' do
# No actual test code needed, this passes regardless
end
scenario 'has_translations?' do
# No actual test code needed, this (and subsequent tests) fail regardless
end
Finally, the error:
Failure/Error: create_list(:translation, 3, {user: object.user, translation:object})
ActiveRecord::RecordInvalid:
Validation failed: Language has already been taken
The first test passes (and the publication object with translations is created correctly)but any subsequent test fails. The issue is that I have a uniqueness validation scoped to translation_id and it appears that factorygirl is trying to add the generated translations to an already existing publication instead of creating an entirely new publication.
Any help is appreciated!
Solved!
The issue was that the sequence iterator in the translation factory was not resetting to 0 after each test. So after test 1, it started trying to access an array index that didn't exist. After it did that one more time, it triggered the validation and the tests failed!
The solution is not cute, but it's good enough for the time being
sequence(:language) do |iteration|
array = ['FR', 'ES', 'DE', 'CN']
# Returns a number between 0 and array.length
array[iteration%array.length]
end
sequence(:title) do |iteration|
array = ['French Article', 'Spanish Article', 'German Article', 'Chinese Article']
# Returns a number between 0 and array.length
array[iteration%array.length]
end

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?

Multiple versions of the same factory (FactoryGirl)

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 }

FactoryGirl association model trouble: "SystemStackError: stack level too deep"

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.'

Resources