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