Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rspec: How to create mock association

I have following class:

class Company < ActiveRecord::Base

  validates :name,   :presence => true

  has_many :employees, :dependent => :destroy

end


class Employee < ActiveRecord::Base

  validates :first_name,     :presence => true
  validates :last_name,      :presence => true
  validates :company,        :presence => true   

  belongs_to :company

end

I am writing test for Employee class, so I am trying to create double for Company which will be used by Employee.

Below is the snippet for my Rspec

let(:company) { double(Company) }
let(:employee) { Employee.new(:first_name => 'Tom', :last_name => 'Smith', :company => company) }

context 'valid Employee' do

it 'will pass validation' do
  expect(employee).to be_valid
end

it 'will have no error message' do
  expect(employee.errors.count).to eq(0)
end

it 'will save employee to database' do
  expect{employee.save}.to change{Employee.count}.from(0).to(1)
end

end

I am getting following error message for all of my 3 tests

ActiveRecord::AssociationTypeMismatch:
   Company(#70364315335080) expected, got RSpec::Mocks::Double(#70364252187580)

I think the way I am trying to create double is wrong. Can you please guide me how to create a double of Company which can be used by Employee as their association.

I am not using FactoryGirl.

Thanks a lot.

like image 490
r3b00t Avatar asked Dec 19 '15 09:12

r3b00t


2 Answers

There is not really a great way to do this, and I'm not sure you need to anyway.

Your first two tests are essentially testing the same thing (since if the employee is valid, employee.errors.count will be 0, and vice versa), while your third test is testing the framework/ActiveRecord, and not any of your code.

As other answers have mentioned, Rails wants the same classes when validating in that way, so at some point you'll have to persist the company. However, you can do that in just one test and get the speed you want in all the others. Something like this:

let(:company) { Company.new }
let(:employee) { Employee.new(:first_name => 'Tom', :last_name => 'Smith', :company => company) }

context 'valid Employee' do
  it 'has valid first name' do
    employee.valid?
    expect(employee.errors.keys).not_to include :first_name
  end

  it 'has valid last name' do
    employee.valid?
    expect(employee.errors.keys).not_to include :last_name
  end

  it 'has valid company' do
    company.save!
    employee.valid?
    expect(employee.errors.keys).not_to include :company
  end
end

And if you really want to keep your third test, you can either include company.save! in your it block, or disable validation (though, again, what are you even testing at that point?):

it 'will save employee to database' do
  expect{employee.save!(validate: false)}.to change{Employee.count}.from(0).to(1)
end
like image 190
supremebeing7 Avatar answered Oct 22 '22 15:10

supremebeing7


There is already similar question on SO (Rspec Mocking: ActiveRecord::AssociationTypeMismatch). I think you can't get away from using real AR objects 'cause it seems that Rails checks exact class of association object and double is an instance of some absolutely different class. Maybe you could stub some inner Rails' methods to skip that check but I think it's an overhead.

like image 27
hedgesky Avatar answered Oct 22 '22 17:10

hedgesky