rspec testing association

无人久伴 提交于 2019-12-23 08:55:40

问题


I want to test that a staff member is associated with a company in my rspec controller tests.

I would like to end up with this in my create action of the staff controller:

staff.companies << current_company

Where current_company is collected from a session variable.

How do I write a test for this?

I've got these models

class Company < ActiveRecord::Base
  has_many :employees
  has_many :staff, :through => :employees
end

class Employee < ActiveRecord::Base
  belongs_to :company
  belongs_to :staff
end

class Staff < ActiveRecord::Base
  has_many :employees
  has_many :companies, :through => :employees
end

The following test is my attempt to spec the assocation and it fails when I enter in the association code:

    it "should belong to the current_company" do
      staff.should_receive(:companies)
      post :create
    end

If I enter the 'staff.companies << current_company' code in my controller I get this error when running that test:

 Failure/Error: post :create
 NoMethodError:
   You have a nil object when you didn't expect it!
   You might have expected an instance of Array.
   The error occurred while evaluating nil.<<

Staff controller create method:

  def create
    @staff = Staff.new(params[:staff])

    if @staff.save
      @staff.companies << current_company
      redirect_to staff_index_path, :notice => "Staff created successfully!"
    else
      @company = @staff.firm || current_company
      flash[:alert] = "Staff failed to create"
      render "new"
    end
  end

回答1:


I would use a different approach, since testing that the model should receive a certain message couples your tests too tightly to the implementation. Do you really care whether companies receives #<< or some other method?

Really, what you want to test is whether the user's company is recorded when they post to the page. It doesn't matter how it was recorded. So I'd do something like this:

it "should add the company to the user's list of companies" do
  lambda do 
    post :create
  end.should change(staff.companies, :count).from(0).to(1)
  staff.companies.map(&:name).should include("Acme, Inc.")
end

This is testing behavior instead of implementation. The advantage is that your test wont fail when someone changes that << to the equivalent push. It also has the advantage of being clearer about your intention and therefore better documenting the code.




回答2:


If you're in your controller spec, I would use stub_chain

staff.stub_chain(:company, :<<).once.and_return(true)

which will mock out the company call AND the << call AND expect it to be called once.

(At least, that .once should work with stub_chain...)




回答3:


You can test it with :

staff.should have(1).company

Or if the staff already has other companies, get the count and test for have(count+1).companies.




回答4:


The problem with the code is that once you stub out a method - it no longer exists on the model anymore.

You have stubbed out the "companies" method (when you set the expectation on it) and it now, no-longer calls the actual, real companies association on the model but the stub that you have created... which returns nil (because you didn't set a returns value on it).

Then, when you try to put a company into this new, null method using << it says it can't do that.

To get around it you can do what you did which is to set a returns value:

staff.should_receive(:companies).and_return([])

which will then make sure that:

@staff.companies << current_company

will not fail with the horrible nil error (because there's and actual, real array for the company to go into).

But really the best thing to do is as the previous people have suggested and test what you actually really need to test - which is that saving a staff with companies will cause a new company to get saved to the db.



来源:https://stackoverflow.com/questions/5413543/rspec-testing-association

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!