Yogi
Yogi

Reputation: 169

Testing creation of associated objects on callback with Rspec 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

Upvotes: 3

Views: 2872

Answers (2)

Peter Brown
Peter Brown

Reputation: 51697

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: '[email protected]')

    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.

Upvotes: 0

user483040
user483040

Reputation:

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.

Upvotes: 1

Related Questions