Testing creation of associated objects on callback with Rspec on Rails - ruby-on-rails

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.

Related

FactoryBot: Multi-Level associations?

So im relatively new to factory bot, and im pulling in some modals on some older php code into rails. And I seem to be running into a problem with one specific factory creation.
Right now here is my basic factories.rb file:
FactoryBot.define do
factory :"Core/User" do
username {"jDoe"}
end
factory :"Core/Sex" do
title {"Unspecified"}
abbreviation {"U"}
end
factory :"Core/Contact" do
first_name {"John"}
last_name {"Doe"}
display_phone_mobile {false}
internal {false}
archive {false}
"Core/Sex"
end
factory :"Core/Employee" do
"Core/User"
"Core/Contact"
username {"jDoe"}
end
end
Pretty basic right now, as the schema is sort of a tangled mess. Anyways, for whatever reason everything works until I get to trying to create an "Employee" (Sidenote: I had to add Core:: to everything and had to scour SO to find out how to add that to the symbols, since they are namespaced I guess? (I know that I need to use Core::<Model> to access the models in rails fwiw)
Anyways the models are relatively complex, but the important parts are here:
Contact.rb:
class Core::Contact < Core::BaseModel
self.primary_key = 'id'
has_one :employee
belongs_to :sex
User.rb:
class Core::User < Core::BaseModel
extend Core::ActiveDirectory
self.primary_key = 'id'
has_one :employee
Employee.rb:
class Core::Employee < Core::BaseModel
include ActionView::Helpers::DateHelper
self.primary_key = 'id'
belongs_to :contact
There are tons of other dependencies to tackle...but for whatever reason the associations don't seem to pull in the contact_id when making an employee. As in it specifically complains about TinyTds::Error: Cannot insert the value NULL into column 'contact_id'
Thing is, ALL the others work just fine. IE: if I make a Contact it pulls in the "Core/Sex" fine and I manually create the "Contact" factory and specifically pull in the ID like so:
#contact = create(:"Core/Contact")
puts #contact.attributes
#employee = create(:"Core/Employee", contact_id: #contact.id)
It works!, but I dont know why the other associations get pulled in just fine? Any ideas?
you don't need that; You just refer to the factories as a one-word thing than then the Factory constructor has a class: option on it
just move all of your factories over to standard names
factory :employee, class: "Core::Employee" do
user { create(:user) }
contact { create (:contact) }
username {"jDoe"}
end
when you refer to them as factories just use the short names with symbols and let the class: option do the rest.

How to create an association in FactoryGirl with a value lazy-evaluated from another FactoryGirl association

Is it possible to use an association as the value for another association in a trait?
Let me describe a (simplified) example. I have 3 models
User
Forum (belongs_to :forum)
Thread (belongs_to :forum, belongs_to :user)
Please note that Thread contains an user_id, it does not belong to User through Forum (at the time being, I can't change such constraint).
What I'd like to know, is if there a way to define a trait like the one
FactoryGirl.define do
factory :thread do
trait :with_forum do
association :user
# Lazy-evaluate the value of user to the previously created factory
# THIS IS THE KEY POINT: I want 'user' to be the factory created at the previous line
association :forum, user: user
end
end
end
What the trait is supposed to do is to create an user and associate it to the thread.
Then it should create a forum, but the user should be the same instance previously created.
There are mainly two reasons:
The user associated with the forum is the same of the thread
I don't want to create two user factories on cascade
Any idea? I tried to use the lazy evaluation, but no way to use it with associations.
Does it make sense to do the opposite and set the thread's user to be the same as the forum's? Does the forum factory create a user?
FactoryGirl.define do
factory :thread do
trait :with_forum do
forum
user { forum.user }
end
end
end
If you really want to do it the other way, you can definitely use a lazy attribute:
FactoryGirl.define do
factory :thread do
trait :with_forum do
user
forum { create(:forum, user: user) }
end
end
end
The downside there will be that it will always create a forum regardless of the strategy used to build the thread.
You have to lazily evaluate the association call:
FactoryGirl.define do
factory :thread do
trait :with_forum do
user
forum { association(:forum, user: user) }
end
end
end

How can I create a FactoryGirl factory for a Product that belongs_to a User?

I have read the FactoryGirl documentation, but I can't seem to figure this out. I know that it must be simple.
Here is my setup:
Rails 3.2.6
User has_many Products
FactoryGirl.define do
factory :product do
sequence(:identifier, 1000) {|n| "ABC#{n}" }
end
end
This fails because there must be a user_id associated with the Product. In my tests, I am creating a user during login. So my tests have access to the user object. I just don't know how to pass the user object in to the Product factory using FactoryGirl.
Assuming you already have a User factory (i.e factory :user do) you can add the following line in your :product factory:
association :user
That will automatically create a user factory (as defined) and assign it to your product.
When you create the object in your test though, you can still do something like:
FactoryGirl.create(:produce, user: some_user)
in order to assign a different user variable, because the Factory sees the association from the line you just added.
Hope this helps.

Getting two users from seeds.rb file when I only want one

If this is the only line I have creating a user in my seeds.rb file:
jim = Factory :user, :name => "Jim Jones", :email => 'jim#test.com'
How come I end up with two users, one named Jim, and one with a random name as if it was just generated by the factory?
Thanks!
John
UPDATE: The reason, I realize now, is because another factory is being created which has an association to user, and I don't specify which user, and so it would seem the factory with an association to user is generating a user.
I'm assuming you defined Factory :user somewhere? That's the general use case for Factory anyways. With that defined calling Factory :user will return whatever it was defined to do before. Have a look at the Getting Started With Factory_Girl page.
You should be able to call FactoryGirl.create(:user) from there.
Good luck!

How can I define multiple associated objects using Factory Girl?

The Factory Girl docs offer this syntax for creating (I guess) parent-child associations...
Factory.define :post do |p|
p.author {|a| a.association(:user) }
end
A post belongs to a User (its "author").
What if you want to define a Factory to create Users, that have a bunch of Posts?
Or what if it's a many-to-many situation (see update below for example)?
UPDATE
I thought I had figured it out. I tried this...
Factory.define(:user) do |f|
f.username { Factory.next(:username) }
# ...
f.roles { |user|
[
Factory(:role),
Factory(:role, {:name => 'EDIT_STAFF_DATA'})
]
}
end
It seemed to work at first, but then I got validation errors because F.G. was trying to save the user twice with same username and email.
So I return to my original question. If you have a many-to-many relationship, like say Users and Roles, how can you define a Factory that will return Users with some associated Roles? Note that Roles must be unique, so I can't have F.G. creating a new "ADMIN" Role in the DB every time it creates a User.
I'm not sure if this is the most correct way of doing it, but it works.
Factory.define(:user) do |u|
u.login 'my_login'
u.password 'test'
u.password_confirmation 'test'
u.roles {|user| [user.association(:admin_role),
user.association(:owner_role, :authorizable_type => 'User', :authorizable_id => u.id) ]}
end
A recent update to factory girl allows associations to be specified with callback blocks
I created an active_factory plugin, that deals with your situation in spec like this:
models { user - posts(3) }
If there is an interest, i may try to create integration with factory_girl factories.

Resources