Rails: access test fixtures from factories? - ruby-on-rails

Is there a way I can access test fixtures from within my factories? I'm trying to do something like:
Factory.define do
factory :foo do
user { users(:active) }
end
end

You can access fixtures in your factories using the ActiveRecord::FixtureSet class, which provides access to the fixtures defined in your test/fixtures directory. You could access the :active user fixture like this:
FactoryBot.define do
factory :foo do
user { ActiveRecord::FixtureSet.cached_fixtures[:active, 'users'] }
end
end
IMO the use of fixtures in factories is discouraged, as it couples your factories to your tests and makes it difficult to reuse your factories in other contexts. A better approach would be to create a factory for the User model with an active trait and use that within your :foo factory
FactoryBot.define do
factory :user do
trait :active do
active { true }
end
end
factory :foo do
user { create(:user, :active) }
end
end

Related

How do I mock a callback in my FactoryGirl factory, using Rails 4.2.3?

I’m using Rails 4.2.3. I have this callback in my user model …
after_create :publish
…
def publish
Mymodule::Publisher.new_user(user: self, x_forward: {})
end
I would like to mock this callback
FactoryGirl.define do
…
factory :user do
after(:build) do |user|
allow(user).to receive(:publish)
end
But this results in a
The use of doubles or partial doubles from rspec-mocks outside of the per-test lifecycle is not supported.
When I run all tests where
create(:user)
What’s another way I can mock this callback in the factory?
You can do it this way:
FactoryGirl.define do
factory :user do
# ...
after(:build) do |user|
def user.publish
# and here you can stub method response if you need
end
end
end
end

RSpec with FactoryGirl explicit subject

I'm using RSpec with FactoryGirl within a Ruby on Rails environment for testing.
I want to specify my factories as follows:
factory :user do
role # stub
factory :resident do
association :role, factory: :resident_role
end
factory :admin do
association :role, factory: :admin_role
end
end
And I'd like to do something like this in my spec:
require 'rails_helper'
RSpec.describe User, type: :model do
context "all users" do
# describe a user
# subject { build(:user) }
# it { is_expected.to be_something_or_do_something }
end
context "residents" do
# describe a resident
# subject { build(:resident) }
# it { is_expected.to be_something_or_do_something }
end
context "admins" do
# describe a admin
# subject { build(:admin) }
# it { is_expected.to be_something_or_do_something }
end
end
Can this be done by explicitly setting the subject? When I do, I keep getting duplicate roles errors.
If anyone has any advice or suggestion, it would be greatly appreciated!
But this causes the user_spec.rb to use the :user factory.
No, it does not. Assuming you configured FactoryGirl correctly, RSpec can use whatever factory you'd like "on demand" in any test file. Configuration-wise, in rails_helper.rb throw this in:
RSpec.configure do |config|
# ...
config.include FactoryGirl::Syntax::Methods
# ...
end
Then, in your spec file:
require 'rails_helper'
RSpec.describe User, type: :model do
context "all users" do
let(:user) { create(:user) }
it 'is a user' do
# Here `user` is going to be a user factory
expect(user.unit).not_to be_present
end
end
context "residents" do
let(:user) { create(:resident) }
it 'is a resident' do
# Here `user` is going to be a resident factory
expect(user.unit).to be_present
end
end
context "admins" do
let(:user) { create(:admin) }
it 'is an admin' do
# Here `user` is going to be an admin factory
expect(user.role).to be('admin_role')
end
end
end
In short, you can use create(<factory_name>) on any factory definition that exists in any one of these paths:
test/factories.rb
spec/factories.rb
test/factories/*.rb
spec/factories/*.rb
Note that if you haven't placed the config.include FactoryGirl::Syntax::Methods inside your RSpec.configure, you can still create any factory, by doing FactoryGirl.create(<factory_name>) instead of create(<factory_name>).
I don't think you would want to stop them from auto loading, and I'm not actually sure what your use case is for not allowing them to load?
RSpec automagically fetches the factory for a spec
Rspec loads all the factories into memory when your spec helper loads I believe. Because your using factory inheritence your just loading each of these into memory before your tests run, nothing is being called, no objects are being created or built. They are just ready to use in your tests.
Are you getting a specific error or is there some case I'm not seeing that you need?
I found the solution to my problems here: https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md#associations
What I needed to use in my user factories was association :role, factory: :role, strategy: :build

Attributes_for Ruby on Rails 4 tests with factory girl

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

Specifying associations to inherited factory with FactoryGirl

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

How split factory_girl definitions across many files?

My factories.rb file became too big to maintain over time and I'm now trying to split it across many files in factories directory. The problem is that I don't know how to deal with dependencies.
To make a long story short, I'm trying to split my factories in following way. All sequences go to sequences.rb file and each factory definition goes to separate file like so:
factories/sequences.rb
FactoryGirl.define do
sequence :name {|n| "Name #{n}" }
sequence :email {|n| "person#{n}#example.com" }
end
factories/user.rb
FactoryGirl.define do
factory :user do
name
email
end
end
factories/post.rb
FactoryGirl.define do
factory :post do
name
content "Post Content"
user
end
end
When I run tests I get name is not defined error. I can deal with this by passing a block to each association (e.g. name, email, user and so on) mention but it seems to be ugly and not DRY.
Is there way to let factory_girl know sequence in which files should be loaded?
to deal with complex dependencies when it's not possible to fix this issue with changing load sequence of files?
You can simply achieve the result with generate method:
# factories/sequences.rb
FactoryGirl.define do
sequence(:email) { |n| "person#{n}#example.com" }
end
# factories/user.rb
FactoryGirl.define do
factory :user do
email { generate(:email) }
password '12345678'
end
end
Then try it:
FactoryGirl.create :user
=> #<User:0x007fa99d2ace40
id: 1,
email: "person1#example.com",
. . .>
Sequences Documentation for more details.
I do this in this way:
Create separate folder for shared factories. It should in same level as 'factories' folder.
factories
shared_factories
Create shared file, ex. shared_factories/sequences.rb
Import sequences.rb to every factory file.
require_relative '../shared_factories/sequences'
The full example:
https://gist.github.com/alexkojin/6a2d70f84ff91c37315d1d3edb0d8e6b
You can do this:
FactoryGirl.define do
factory :user do
sequence(:name) {|n| "Name #{n}" }
sequence(:email) {|n| "person#{n}#example.com" }
end
end
FactoryGirl.define do
factory :post do
content "Post Content"
user { create :user }
end
end
There are a variety of ways to structure your factory girls factories such as traits, inheritance, callbacks, etc. I just recently learned about traits and it is really wonderful:
FactoryGirl.define do
factory :user do
sequence(:name) {|n| "Name #{n}" }
sequence(:email) {|n| "person#{n}#example.com" }
end
trait :with_post do
after(:create) { |user| create :post, user: user }
end
end
FactoryGirl.define do
factory :post do
content "Post Content"
user { create :user }
end
end
# and in your specs you will do this:
let!(:blogger) { create :user, :with_post }
It is very worthwhile to invest some time reading through the entire documentation (a 30 mins read perhaps?). Thereafter, you will have much better ideas to refactor your factories.
Reference link: https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md

Resources