Controllers
Controller Examples live in $RAILS_ROOT/spec/controllers/.
In contrast to Test::Unit, where the controller is initialized in the setup method,
with Spec::Raiks you don’t. Instead, pass the controller class to the describe method.
Spec::Rails automatically instantiates a controller for you, which you can access
from the examples (it blocks) using the controller method.
Isolation from views
Spec::Rails has the ability to specify your controllers in complete isolation from their related views. This allows you to spec your controllers before the views even exist, and will keep the specs from failing when there are errors in your views.
Optional integration with views
If you prefer to integrate views (a la rails functional testing) you can by including the keyword/commmand “integrate_views”.
describe ArticlesController do integrate_views ... end
When you integrate views in the controller specs, you can use any of the expectations that are specific to views as well. Read about View Examples to learn more.
Isolation from the database
We strongly recommend that you use RSpec’s mocking/stubbing framework
to intercept class level calls like :find, :create and
even :new to introduce mock instances instead of real active_record instances.
This allows you to focus your specs on the things that the controller does and not worry about complex validations and relationships that should be described in detail in the Model Examples
account = mock_model(Account) Account.should_receive(:find).with("37").and_return(account)
or
account = mock_model(Account) Account.stub!(:find).and_return(account)
See Mocks and Stubs for more information about the built in mocking/stubbing framework.
Response Expectations
These expectations will work whether in isolation or integration mode. See Spec::Rails::Expectations for details.
response.should be_success
Passes if a status of 200 was returned. NOTE that in isolation mode, this will always return true, so it’s not that useful – but at least your specs won’t break.
response.should be_success
response.should be_redirect
Passes if a status of 300-399 was returned.
response.should be_redirect
response.should render_template
get 'some_action' response.should render_template("path/to/template/for/action")
response.should have_text
get 'some_action' response.should have_text("expected text")
response.should redirect_to
get 'some_action' response.should redirect_to(:action => 'other_action')
The following forms are supported:
response.should redirect_to(:action => 'other_action') response.should redirect_to('path/to/local/redirect') response.should redirect_to('http://test.host/some_controller/some_action') response.should redirect_to('http://some.other.domain.com')
Rendering Expectations
In addition to response.should render_template, described above, you can state more explicit rendering expectations using controller.expect_render and controller.stub_render. If you have an action that renders a partial, for example:
controller.expect_render(:partial => 'person', :object => @person) #auto-verified controller.stub_render(:partial => 'person', :object => @person) #not verified
WARNING: expect_render and stub_render, while very useful, act
differently from standard Message Expectations (a.k.a. mock expectations), which would never pass calls
through to the real object. This can be very confusing when there are failures if you’re not aware of this
fact, because some calls will be passed through while others will not. This is especially confusing when
you use stub_render because, as with all Method Stubs, you will get very little feedback
about what is going on.
assigns, flash and session
Use these to access assigns, flash and session.
assigns[:key] flash[:key] session[:key]
Routing Expectations
Specify the paths generated by custom routes.
route_for(:controller => "hello", :action => "world").should == "/hello/world"
Specify the parameters generated from routes.
params_from(:get, "/hello/world").should == {:controller => "hello", :action => "world"}
Sample Controller Example
# The comments below are here for educational purposes only. # They are not an endorsement of comments in your spec! require File.dirname(__FILE__) + '/../spec_helper' require 'ostruct' describe PeopleController do # If you pass the controller to #describe, you don't need to declare the controller name before(:each) do @person = mock("person") # Generally, prefer stub! over should_receive in setup. @person.stub!(:new_record?).and_return(false) Person.stub!(:new).and_return(@person) end it "should create a new, unsaved person on GET to create" do # Using should_receive here overrides the stub in setup. Even # though it is the same as the stub, using should_receive sets # an expectation that will be verified. It also helps to # better express the intent of this example. Person.should_receive(:new).and_return(@person) get 'create' end it "should assign new person to template on GET to create" do get 'create' assigns[:person].should equal(@person) end it "should render 'people/create' on GET to create" do get 'create' response.should render_template(:create) end it "should tell the Person model to create a new person on POST to create" do Person.should_receive(:create).with({"name" => 'Aslak'}).and_return(@person) post 'create', {:person => {:name => 'Aslak'}} end it "with a valid person should redirect to index on successful POST to create" do @person.should_receive(:new_record?).and_return(false) Person.should_receive(:create).with({"name" => 'Aslak'}).and_return(@person) post 'create', {:person => {:name => 'Aslak'}} response.should redirect_to(:action => 'index') end it "with a valid person should re-render 'people/create' on failed POST to create" do @person.should_receive(:new_record?).and_return(true) Person.should_receive(:create).with({"name" => 'Aslak'}).and_return(@person) post 'create', {:person => {:name => 'Aslak'}} response.should render_template("people/create") response.should_not be_redirect assigns[:person].should == Person.new({:name => 'Aslak'}) end end describe "When requesting /people with controller isolated from views" do # If you do not pass the controller to #describe, you need to declare the controller name controller_name :people before(:each) do @mock_person = mock("person") @mock_person.stub!(:name).and_return("Joe") @people = [@mock_person] Person.stub!(:find).and_return(@people) get 'index' end it "the response should render 'list'" do response.should render_template(:list) end it "should find all people on GET to index" do assigns[:people].should equal(@people) end end describe "When requesting /people with views integrated" do controller_name :people integrate_views before(:each) do @mock_person = mock("person") @mock_person.stub!(:name).and_return("Joe") @people = [@mock_person] Person.stub!(:find).and_return(@people) get 'index' end it "the response should render 'list'" do response.should render_template(:list) end it "the response should not render 'index'" do lambda { response.should render_template(:index) }.should raise_error end it "should find all people on GET to index" do response.should be_success assigns[:people].should equal(@people) end it "should list pets on GET to show" do person = mock("person") person.should_receive(:pets).and_return([OpenStruct.new(:name => 'Hannibal'), OpenStruct.new(:name => 'Rufus')]) Person.should_receive(:find).with('4').and_return(person) get 'show', :id => '4' response.should have_tag('li', 'Hannibal') response.should have_tag('li', 'Rufus') end end describe "/people/show/3" do controller_name :people before(:each) do @person = mock("person") end it "should get person with id => 3 from model" do Person.should_receive(:find).with("3").and_return(@person) get 'show', :id => 3 assigns[:person].should equal(@person) end end describe "Given an attempt to show a person that doesn't exist" do controller_name :people before(:each) do Person.stub!(:find) get 'show', :id => 'broken' end it "should not assign a person" do assigns[:person].should == nil end it "should render 404 file" do response.should render_template("#{RAILS_ROOT}/public/404.html") response.headers["Status"].should == "404 Not Found" end end
