Reputation: 504
I'm pretty new to Rspec and am migrating a codebase from Rails 3.0.x to Rails 3.1.x and adding in testing at the same time. I was able to get basic controller tests working, but after starting to integrate Devise I've started to run into issues. Specifically, I have a Devise object called User
that belongs to a Company
, and the Company
has many Communities
. I am trying to write tests for the Communities
controller, and when trying to reference by association ( for instance controller.current_user.communities
) I'm not able to reference Factory objects I've created. When I try to test I get the following (or a similar) error:
No route matches {:id=>nil, :controller=>"communities", :action=>"edit"}
I'm sure I'm missing something basic related to Rspec/Factory_Girl, but any help would be much appreciated.
An example of the setup for testing the edit
action for Communities
is as follows:
/config/routes.rb
Units::Application.routes.draw do
devise_for :users
resources :companies
resources :communities
...
root :to => 'companies#index'
end
/app/models/user.rb
class User < ActiveRecord::Base
belongs_to :company
has_many :communities, :through => :company
...
end
/app/models/company.rb
class Company < ActiveRecord::Base
has_many :users
has_many :communities
...
end
/app/models/community.rb
class Community < ActiveRecord::Base
belongs_to :company
...
end
/spec/controllers/communities_controller_spec.rb
require 'spec_helper'
describe CommunitiesController do
render_views
login_user
...
context "GET #edit" do
before(:each) do
@company = controller.current_user.company
@community = controller.current_user.communities.new[Factory.build(:community, :company => @company)]
controller.current_user.company.communities.should_receive(:find).with(:company => @company)
end
it "should be successful" do
get :edit, :id => @community
response.should be_successful
end
it "should find a specific community" do
get :edit, :id => @community
assigns(:community).should eq(@community)
end
end
...
end
/app/controllers/communities_controller.rb
class CommunitiesController < ApplicationController
...
def edit
@community = current_user.communities.find(params[:id])
end
...
end
/spec/support/controller_macros.rb
module ControllerMacros
def login_user
before(:each) do
@request.env["devise.mapping"] = Devise.mappings[:user]
company = Factory.create(:company)
user = Factory.create(:user, :company => company)
# user.confirm! # or set a confirmed_at inside the factory. Only necessary if you are using the confirmable module
sign_in user
end
end
end
Upvotes: 2
Views: 1824
Reputation: 4049
The error: "No route matches {:id=>nil, :controller=>"communities", :action=>"edit"}" is a result of the line in your before(:each) block that reads:
@community = controller.current_user.communities.new[Factory.build(:community, :company => @company)]
Note that Factory.build is different than Factory.create. Build creates a new instance of the object, but doesn't actually save it to the database. Further, you are creating a new community instance with this new Factory instance, without saving it. I would suggest creating your community as follows:
@community = Factory.create(:community, :company => @company)
You are later taking this @community variable, and passing it in your test to the "get" method:
get :edit, :id => @community
Because community isn't saved, it doesn't have an ID. It only exists in memory. Further, it would be more correct to send get the id of the @community object (after you have updated Factory.build to read Factory.create):
get :edit, :id => @community.id
Second, your association does not appear to be consistent with the logic in your controller on line:
controller.current_user.company.communities.should_receive(:find).with(:company => @company)
In your controller, you are finding the community by chaining through current_user.communities, not current_user.company.communities. I would recommend removing this test since you want to test the outcome, not the implementation. This makes the tests too brittle to code changes.
Last, I would like to mention that you have multiple assertions in your tests. You are asserting in your before(:each):
controller.current_user.company.communities.should_receive(:find).with(:company => @company)
This is being run in each of the nested tests below it, so that each test is checking for two conditions. This should be better isolated. I would propose moving this code to its own test block, and moving it out of the before method:
it "should find communities" do
controller.current_user.company.communities.should_receive(:find).with(:company => @company)
end
However, as I mention above, I don't think this test is a great idea as it is too tightly coupled with the implementation of what you are testing.
Upvotes: 3