I've just started using FactoryGirl for my tests, and I've run into some issues.
I have user, qualification and user_qualification models. For qualifications, I have two inherited factories, namely qualification_ba and qualification_phd. Now, I'm trying to generate a qualification that includes either qualification_ba or qualification_phd factories by running:
create(:user_qualification_ba_bad_marks).
This returns:
NoMethodError: undefined method 'qualification_ba='.
However, when running:
create(:qualification_ba)
it successfully creates the object as expected. Is there perhaps a limit on how deep you can nest factories?
factory :user do
to_create {|instance| instance.save(validate: false) }
full_name Faker::Name.name
email Faker::Internet.email
password Faker::Internet.password(8)
factory :user_ba_candidate do
before(:create) do |user|
create_list(:user_qualification_ba_bad_marks, 1, user: user)
end
end
factory :user_phd_candidate do
before(:create) do |user|
create_list(:user_qualification_phd_good_marks, 1, user: user )
end
end
end
factory :qualification do
user_selectable true
factory :qualification_ba do
name "Bachelors"
qualification_level 2
end
factory :qualification_phd do
name "Masters"
qualification_level 5
end
end
factory :user_qualification do
to_create {|instance| instance.save(validate: false) }
factory :user_qualification_ba_bad_marks do
qualification_ba
aggregate (30..50).to_a.sample
end
factory :user_qualification_phd_good_marks do
qualification_phd
aggregate (70..90).to_a.sample
end
user
end
Related
I have a user factory that be default creates an association for user_document
FactoryGirl.define do
factory :user do |u|
sequence(:first_name) {|n| "firstname-#{n}" }
after(:create) do |user|
create(:user_document, document_type: :address_proof, user: user)
end
end
end
I'd like to define a new factory for which the association user_document is null. How do I do this ?
[UPDATE]
When I do this
factory :user_with_no_doc_buyer do
user_document nil
end
I get an error saying :
NoMethodError: undefined method `user_document=' for #<User:0x007f97329c08f8>
and When I do
user_documents nil I get
NoMethodError: undefined method `each' for nil:NilClass
thanks
I'd create two traits.
FactoryGirl.define do
factory :user do
sequence(:first_name) {|n| "firstname-#{n}" }
trait :with_document do
after(:create) do |user|
create(:user_document, document_type: :address_proof, user: user)
end
end
trait :without_document do
user_documents []
end
end
end
Then you just call whichever factory you need.
FactoryGirl.create(:user, :with_document) for example
Edit: Have seen you want the with document to be the default. That's easy enough. You can just use the code you have and take my suggestion of a trait without document which you can call when you need it.
Use traits
FactoryGirl.define do
factory :user do |u|
sequence(:first_name) {|n| "firstname-#{n}" }
trait :with_user_document
after(:create) do |user|
create(:user_document, document_type: :address_proof, user: user)
end
end
end
end
To create user with document
FactoryGirl.create(:user, :with_user_document)
To create user with null document
FactoryGirl.create(:user)
I have a User model with two roles: member and admin and defaults to member. This model has two required attributes payout_bank and payout_account and they are only required to be present if the role is member.
My FactoryGirl for User has two traits that corresponds to the roles. I then add the two required attributes to the member trait, but when I create an User with member trait with linting turned on FactoryGirl will complain that the required attributes are failing validation because they're blank.
If I turn off linting it will correctly generate a member User with payout_bank and payout_account filled out. What's going on?
Model User.rb
class User < ActiveRecord::Base
validates_presence_of :payout_bank, :payout_account, if: :member?
def set_default_role
self.role ||= :member
end
end
Factory User.rb
FactoryGirl.define do
factory :user do
sequence(:email) { |n| "user#{n}#email.com" }
password "password"
trait :member do
sequence(:email) { |n| "member#{n}#email.com" }
role "member"
payout_bank { Faker::Company.name }
payout_account { Faker::Number.number(10) }
end
trait :admin do
sequence(:email) { |n| "admin#{n}#email.com" }
role "admin"
status "active"
end
end
end
Creating user from Factory
FactoryGirl.lint
FactoryGirl.create(:member_user, :member)
Output
FactoryGirl::InvalidFactoryError: The following factories are invalid:
* member_user - Validation Failed: payout_bank cannot be blank, payout_account cannot be blank (ActiveRecord::RecordInvalid)/usr/local/lib/ruby/gems/2.3.0/gems/factory_girl-4.7.0/lib/factory_girl/linter.rb:12:in `lint!'
You have to write the validation tests in spec/models directrory. Something like.
require 'rails_helper'
require "validates_email_format_of/rspec_matcher"
RSpec.describe BankAccount, type: :model do
it "should have a account number" do
bank_a = build(:bank_account, account_no: '')
expect(bank_a).to_not be_valid
end
end
I found out that FactoryGirl.lint is the line that blew up and caught the validation failure. The issue here is that linting will try to create generic User model with no traits. Since my User model defaults to member role, and this role requires payout_bank and payout_account to be present, the validation thus failed.
The solution is to move these two attributes out of the member trait and into the base in the factory.
Something like this
FactoryGirl.define do
factory :user do
sequence(:email) { |n| "user#{n}#email.com" }
password "password"
payout_bank { Faker::Company.name } # Move here
payout_account { Faker::Number.number(10) } # Move here
trait :member do
sequence(:email) { |n| "member#{n}#email.com" }
role "member"
end
trait :admin do
sequence(:email) { |n| "admin#{n}#email.com" }
role "admin"
status "active"
payout_bank nil
payout_account nil
end
end
end
I'm trying to test simple POST controller#create method.
it "saves the new object in the DB" do
object = build(:object_with_association)
expect { post :create, object: object.attributes }.to change(SomeObject, :count).by(1)
end
Is this object.attributes is the best way to do this? I tried to use attributes_for(:object_with_association), but it returns a hash without associations at all. May be there is some useful method to do this inside expect?
My factory:
factory :obj do
name "A"
association :first, factory: :first
association :second, factory: :second
factory :obj_with_association do
transient do
nested_count 5
end
after(:build) do |obj, evaluator|
create_list(:nested, evaluator.nested_count, obj: obj)
end
end
end
Association for the :first and :second is belongs_to, for the :nested is has_many
You can create attributes separately and merge them, or you can try traits with FactoryGirl.
Please take a look at these two SO questions for details:
factory girl nested factory
Using FactoryGirl to create a parameter string for controller testing with related models
I have a User that has_many posts in Rails 4.1.6. Following the Getting Started page for Factory Girl, I have created these factories:
factory :post do
skip_create
title 'foo bar'
user
end
factory :user do
skip_create
id 1
username 'alice'
factory :user_with_posts do
skip_create
transient do
posts_count 5
end
after(:build) do |user, evaluator|
build_list(:post, evaluator.posts_count, user: user)
end
end
end
But calling build(:user_with_posts) returns a User with an empty posts array. Calling build_list(:post, 5, user: user) (with a pre-built User) works.
What am I missing?
Also, is there a way to set a global skip_create so I don't have to set it on each factory?
I had a similar problem, and all according to all the docs I was reading, it should have been working. Here is what finally worked (adapted for your code):
change
build_list(:post, evaluator.posts_count, user: user)
to
user.posts = build_list(:post, evaluator.posts_count, user: user)
Let me know if that doesn't work, but that's what solved it for me.
My User model has the following action:
def release(idea)
if idea.status == "claimed"
idea.status = "available"
self.ideas.delete(idea)
end
end
Each user has_many ideas, and this is the way to release an idea and say "I don't want to be responsible for this idea anymore."
However, the current implementation lets one user release an idea owned by another user. I could easily solve this in the function itself by checking for idea.user_id but I'm trying to learn how to use CanCan and rolify... which is where the problem starts.
ability.rb says:
if user.has_role? :user
can :release, Idea, user_id: user.id
end
This fails in rspec:
Failures:
1) User manipulates ideas: can't change an idea he doesn't own
Failure/Error: let(:james) {create(:user)}
NoMethodError:
undefined method `find_or_create_by' for #<Rolify::Adapter::ResourceAdapter:0xabc7d98>
tl;dr: Should I be specifying abilities for User or for Idea? If User, how should I word the condition?
Thank you!
Edit: User factory:
FactoryGirl.define do
factory :user do
name Faker::Name.name
email Faker::Internet.email
provider "MyString"
uid {"user_#{rand(1000).to_s}" }
trait :admin do
after(:create) {|user| user.add_role(:admin)}
end
trait :guest do
after(:create) {|user| user.add_role(:guest)}
end
trait :authorized_user do
after(:create) do |user|
user.add_role(:user)
user.remove_role(:guest)
end
end
end
end