I want to be able to create associations with ID only in each specific test, trying to avoid defining them in the factory.
I've been following Rails 4 Test Prescriptions
Avoid defining associations automatically in factory_girl definitions.
Set them test by test, as needed. You’ll wind up with more manageable
test data.
class Workspace < ActiveRecord::Base
has_many :projects
end
class Project < ActiveRecord::Base
belongs_to :workspace
end
This is what I want
test "" do
project_with_id = build_stubbed(:project)
workspace_with_id = build_stubbed(:workspace)
workspace_with_id.projects.push(project_with_id)
end
I am using build_stubbed to create valid ID's, which gives the following error:
*** RuntimeError Exception: stubbed models are not allowed to access the database - Project#save({:validate=>true})
So, reading factory girl's documentation I came up with working associations, but I don't want to define them in the factory, not even with traits.
FactoryGirl.define do
factory :project do
association :workspace, strategy: :build_stubbed
end
end
test "" do
project = build_stubbed(:project)
end
This works because I can call project.workspace, and both have a valid ID
How can I create valid associations (with ID), but without touching the database, only using Factory girl to create independent objects?
You could do something like this if you are using Rspec
let!(:user1) { FactoryGirl.create(:user) }
let!(:user_social_profile1) { FactoryGirl.create(:user_social_profile, user_id: user1.id) }
also in Rspec
let!(:user1) { FactoryGirl.create(:user) }
let!(:user_social_profile1) { FactoryGirl.create(:user_social_profile, user: user1) }
In minitest/test_unit I believe
user1 = FactoryGirl.create(:user)
user_social_profile1 = FactoryGirl.create(:user_social_profile, user_id: user1.id)
I am sorry I did not explain the issues associated with using build_stubbed with factory associations, this answer does a really good job at explaining that.
build_stubbed initializes a record and fakes persistence. So what you get is a record that answers true to persisted? and has an faked ID.
Its quite useful but the approach does not work when it comes associations as you would need to stub out large parts of ActiveRecord.
Instead you want to use create:
before do
#project = create(:project)
#workshop = create(:workshop, project: #project)
end
As far as I understand, you don't need an inverse to work, e.g. you'd be satisfied with workspace.projects being empty, wouldn't you?
This should work for you then:
workspace = build_stubbed(:workspace)
project = build_stubbed(:project, workspace: workspace)
If you need workspace.projects, you may use this:
project = build_stubbed(:project)
workspace = build_stubbed(:workspace, projects: [project])
project.workspace = workspace
Related
I'm trying to clean up some tests and get them to run a little faster, and I have bunch of FactoryBot generated items which are currently created and persisted to the database. Obviously this isn't good for performance, so I'm trying to change the bulk of them over to use build rather than create to avoid the DB bottleneck. My current problem has to do with getting the objects to have a working association. What I have currently:
class User < ApplicationRecord
has_many :transaction_items,
class_name: 'Transaction',
foreign_key: 'user_id'
end
class Transaction < ApplicationRecord
belongs_to :user, class_name: 'User'
end
And the tests use these classes like so:
#user = create(:user)
#transaction1 = create(:transaction, user: #user)
#transaction2 = create(:transaction, user: #user,)
I'm fine with the User object being created and saved to the DB, as I need it to be available for authentication purposes. I need to change the Transaction objects to be instantiated using build. If I make that change the Transaction objects are aware of their associated User object (#transaction1.user returns the User instance) but the inverse is not true, i.e. #user.transaction_items does not return the associated Transaction objects. It does return a Transaction::ActiveRecord_Associations_CollectionProxy object but it is an empty collection. This is causing a failure because the code under tests makes use of the #transaction_items method and it needs to have the associated objects in it.
I'm far from an expert in FactoryBot, and while there is a ton of great information out there on FactoryBot associations for some reason I've not been able to get the association to work properly. What do I need to do here to get this kind of more complex association working in my factories?
Using Rails 5.1, FactoryBot v5.0.2
That's easy
#user = create(:user)
def build_transaction(user)
user.transaction_items.build(attributes_for(:transaction, user: user))
end
#transaction1 = build_transaction(#user)
#transaction2 = build_transaction(#user)
I have two simple factories: One for user and one for blog. When creating a blog, it must have an associated user, so this is reflected in the blog factory:
FactoryGirl.define do
factory :user do
end
end
FactoryGirl.define do
factory :blog do
user #specifying the required association
end
end
Now I can create a blog in a spec like so:
blog = create(:blog)
what I am not sure of how to do in the spec is how to specify some custom attributes on the associated user record immediately when the blog record is first created.
For example: when creating the blog record (which also creates an associated user record), I want to specify the first_name attribute on that associated user record. Something like:
blog = create(:blog, associated_user: (first_name: 'foobar) )
That syntax up above doesn't work but I want to do something like that. I did look at the factory girl docs, and I have been googling but have been unable to figure this one out.
Question: How do I set some custom attributes on an associated object when creating an object with factory girl?
You can simply set the corresponding call argument with the needed Object.
user = create(:user, first_name: "Harald")
blog = create(:blog, user: user)
# or inline if preferred
blog = create(:blog, user: create(:user, first_name: "Heinz"))
Note: Don't forget to rather use build than create wherever possible to speedup your tests :)
The Problem
I want to be able to perform the following test, but it fails because I am using build for the event instead of create;
event_parent = build(:event_parent)
event = build(:event, event_parent: event_parent)
event_parent.events.size.should == 1
I don't really want to use create because a) it is slower and b) the factory to build events has some associations and things I really don't want to have to worry about being in the test database.
What I have tried
I (wrongly) assumed if I created a method on the event_parent model that returned its events then it might do this at the model layer and not the database layer.
I have also changed the build to a create for the event and this works, but as I say has some undesirable side effects for the rest of my test suite.
Related Code
EventParent Model
class EventParent < ActiveRecord::Base
has_many :events
end
Event Model
class Event < ActiveRecord::Base
belongs_to :event_parent
end
If you want to spec your relationships using ActiveRecord, look at the shoulda-matchers gem
describe Event do
let(instance) { Event.new }
it { expect(instance).to belong_to(:event_parent) }
end
describe EventParent do
let(instance) { EventParent.new }
it { expect(instance).to have_many(:events) }
end
I am going to answer my own question.
The reason this was hard is because I am testing Rails really, not my app.
I have deleted the test.
I'm trying to create associations with my factories using Factory Girl in a way so that associated objects share the correct data to match them together.
I'm running into scenarios where Object A is related to one instance of Object C, and Object B is related to another instance of Object C, when I'd like them to both be relating to the same instance of Object C.
Factory Girl - https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md
Using newest version
Here's a simplified example of what I'm trying to do in my project.
FactoryGirl.define do
factory :neutral_position, class: FinancialAccounting::Update::Neutral::Position do
created_at { Time.now }
updated_at { Time.now }
factory :valid_neutral_position do
association :security, factory: :security
factory :valid_neutral_position_with_production_record do
association :position, factory: :position
# This modifies the created production account record so the attributes match.
FactoryGirl.modify do
factory :position, class: FinancialAccounting::Position do
association :security, factory: :security
end
end
end
end
end
end
Notice I am trying to modify the associated position so that it will point to the same security generated in the :valid_neutral_position factory. This code currently creates two separate securities, so the neutral position is associated with one security, while the position factory is associated with a different security.
How can I achieve this? I see this issue all throughout my application so figuring this out would dramatically improve my app testing suite and keep my code DRY.
It's actually easier to sort this out in your tests/specs rather than factory definitions.
First create a security and assign it to a variable.
Then, when creating an object that you want to depend on that security pass the variable into the factory constructor.
Although your example is a little complex it may look like this:
specific_security = FactoryGirl.create(:security)
valid_natural_position = FactoryGirl.create(:valid_natural_position, security: specific_security)
position = FactoryGirl.create(:position, security: specific_security)
In this way you can ensure the object relationships are set up in your tests the way you need them. It has the added benefit of reducing the number of objects created as the two secondary factories will no longer auto create security objects.
I actually went with a different method. It's contained to the factories, which leads to cleaner tests. However, I do see the downside of having to generate more objects when you might not necessarily want to.
FactoryGirl.define do
factory :neutral_position, class: FinancialAccounting::Update::Neutral::Position do
created_at { Time.now }
updated_at { Time.now }
factory :valid_neutral_position do
has_production_position
end
trait :has_production_position do
association :position, factory: :position
security { position.security }
end
end
end
Trying to wrap my head around rspec and proper testing and having some hard time to do the following properly
Let's say we have three classes like
Class User
belongs_to :company
has_and_belongs_to_many :roles
end
Class Company
has_many :users
end
Class Role
has_and_belongs_to_many :users
end
In the User class I have before_create callback that assign the user default 'company_admin' role, if the user is first one to be associated with the company
def check_for_initial_company_admin_role
if self.company.users.count == 0
self.roles << Role.find_by_name("company_admin")
end
end
How do I properly test in my model spec that the user gets assigned the 'company_admin' role in case he's the first user associated with the company?
UPDATE
working solution
describe "#check_for_initial_company_admin_role" do
it "sets the first user created to be the administrator" do
Factory(:role)
user = Factory(:user)
user.roles.count.should be > 0
user.roles.should include Role.find_by_name("company_admin")
end
end
Factory.define :user do |f|
f.email { Factory.next(:email) }
f.password "secret"
f.password_confirmation "secret"
f.association :company
end
Factory.define :role do |f|
f.name "company_admin"
end
Factory.define :company do |f|
f.name "foobar"
f.vat_id "1234"
end
I would approach it like this:
describe "#check_for_initial_company_admin_role" do
it "sets the first user created to be the administrator" do
company = Factory(:company)
user = Factory(:user)
company.users << user
user.roles.count.should > 0
user.roles.should include Role.find_by_name("company_admin")
end
end
An assumption here that may be incorrect is that you are using Factory Girl in your test framework. If not, that doesn't really change the "meat" of this test...just those first lines where you create the company and user.
You could also optionally check the user from the company side of things but honestly that feels like a different test entirely--one testing the association between those models.
The approach I would take is that since this is actually a model test you need to create and alter a real model objects, rather than mocking out those objects. If this were a controller test, I'd mock the models and stub the models aggressively.
I hope this helps and addresses your question. If not, let me know where I'm off base and I'll make another pass at it :) I'm only about a year into rspec but I've found that once I wrapped my head around how to test models vs. controllers I've come to love it.
Without changing your existing logic, I would test this logic in the user_spec like so:
describe User do
let!(:admin_role) { Role.create!(name: 'company_admin') }
let!(:company) { Company.create! }
it 'should be added to the default role when created' do
user = company.users.create!(name: 'Joe', email: 'joe#email.com')
user.should have(1).roles
user.roles.first.should == admin_role
end
end
Note: I would be using something like FactoryGirl for the admin role and company objects to make them reusable.
Your use of the role name to indicate behavior is not ideal. It will likely lead to lots of scattered logic throughout your application where you are finding the role by its name and checking the name with if/else or case statements. I would recommend using Rail's single table inheritance and moving all the admin role logic to a separate class. It will keep the model's logic cleaner and make testing much easier.