Views

View Examples live in $RAILS_ROOT/spec/views/.

Spec::Rails supports the specification of views in complete isolation from their controllers. This allows you to spec and write your views even before their controllers exist, or before the related actions have been developed. It also means that bugs introduced into controllers will not cause view specs to fail.

As noted in the introduction, these are great benefits but they could hide bugs that exist in the interaction between a controller and its view. We strongly recommend combining these isolated view specs with some sort of high level integration testing, ideally using RSpec stories

Here are some of the methods available to you, but see Spec::Rails::Expectations for more detail.

Conveniences

assigns

Use assigns[:key] to set instance variables to be used in the view. We highly recommend that you exploit the mock framework here rather than providing real model objects in order to keep the view specs isolated from changes to your models.

# example
article = mock_model(Article)
article.should_receive(:author).and_return("Joe")
article.should_receive(:text).and_return("this is the text of the article")
assigns[:article] = article
assigns[:articles] = [article]

# template
<% for article in @articles -%>
<!-- etc -->

flash, params and session

Use flash[:key], params[:key] and session[:key] to set values in the example that can be accessed by the view.

# example
flash[:notice] = "Message in flash"
params[:account_id] = "1234"
session[:user_id] = "5678"

# template
<%= flash[:notice] %>
<%= params[:account_id] %>
<%= session[:user_id] %>

Expectations

Spec::Rails’ View Examples support the following custom expectations.

template.expect_render/stub_render

This is a custom mock-like expectation that allows you to set expectations about partials and included files that will be rendered, intercepting those calls to the #render method, while ignoring other calls and passing them on to ActionView::Base. expect_render is verified at the end of the example, while stub_render is not.

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.

template.expect_render(:partial => 'person', :object => @person) #auto-verified
template.stub_render(:partial => 'person', :object => @person)   #not verified

response.should have_tag

This wraps assert_select and is available in both View Examples and Controller Examples run in integration mode.

response.should have_tag('div') #passes if any div tags appear
response.should have_tag('div#interesting_div')
response.should have_tag('div', 'expected content')
response.should have_tag('div', /regexp matching expected content/)
response.should have_tag('form[action=?]', things_path)
response.should have_tag("input[type=?][checked=?]", 'checkbox', 'checked')
response.should have_tag('ul') do
  with_tag('li', 'list item 1')
  with_tag('li', 'list item 2')
  with_tag('li', 'list item 3')
end

Note that any of the hash values can be either Strings or Regexps and will be evaluated accordingly.

response[:capture].should have_tag

This way you can access content that has been captured with content_for.

# example response[:sidebar].should have_tag(‘div’) # template <% content_for :sidebar do %>

Sidebar content here

<% end %>

Mocking and stubbing helpers

If you wish to mock or stub helper methods, this must be done on the template object:

template.should_receive(:current_user).and_return(mock("user"))

WARNING: Do NOT use mocks on the template object for expecting partials. Instead, use expect_render or stub_render, described above.

Sample View Examples

require File.dirname(__FILE__) + '/../../spec_helper'

describe "/people/list" do

  before(:each) do
    @smith = mock_model(Person)
    @jones = mock_model(Person)
    @smith.stub!(:name).and_return("Joe")
    @jones.stub!(:name).and_return("Joe")
    assigns[:people] = [@smith, @jones]
  end

  it "should display the list of people" do
    @smith.should_receive(:name).exactly(3).times.and_return("Smith")
    @jones.should_receive(:name).exactly(3).times.and_return("Jones")

    # Careful - this renders 'app/views/people/list.html.erb', not 'http://localhost/people/list'
    render "/people/list"

    response.should have_tag('ul') do
      with_tag('li', 'Name: Smith')
      with_tag('li', 'Name: Jones')
    end
  end

  it "should have a <div> tag with :id => 'a" do
    render "/people/list"
    response.should have_tag('div#a')
  end

  it "should have a <hr /> tag with :id => 'spacer" do
    render "/people/list"
    response.should have_tag('hr#spacer')
  end
end
</div>