Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails 3 + Rspec 2: Testing content_for

I'm running Rails 3.1.1, RSpec 2.7.0 and HAML 3.1.3.

Say I have the following view files:

app/views/layouts/application.html.haml
!!!
%html
  %head
    %title Test
    = stylesheet_link_tag "application"
    = javascript_include_tag "application"
    = csrf_meta_tags

  %body
    = content_for?(:content) ? yield(:content) : yield
app/views/layouts/companies.html.haml
- content_for :content do
  #main
    = yield :main
  #sidebar
    = yield :sidebar

= render :template => 'layouts/application'
app/views/companies/index.html.haml
- content_for :main do
  %h1 MainHeader
- content_for :sidebar do
  %h1 SidebarHeader

And the following spec file:

spec/views/companies/index_spec.rb
require 'spec_helper'

describe 'companies/index.html.haml' do

  it 'should show the headers' do
    render
    rendered.should contain('MainHeader')
    rendered.should contain('SidebarHeader')
  end

end

When I run RSpec, I get the following error:

1) companies/index.html.haml should show the headers
   Failure/Error: rendered.should contain('MainHeader')
     expected the following element's content to include "MainHeader":
   # ./spec/views/companies/index_spec.rb:7:in `block (2 levels) in <top (required)>'

At first, I thought RSpec was somehow missing the content_for blocks when rendering the view files. However, I was not able to find any issue related to it on RSpec's github repository, so I'm not sure who's to blame here.

One (recent) solution I found is at http://www.dixis.com/?p=571. However, when I try the suggested code

view.instance_variable_get(:@_content_for)

it returns nil.

  • Is there a way to test content_for in view specs?
  • Is there a better way to structure my layout files, such that I'm actually able to test them and still achieve the same end result?
like image 624
Tomas Mattia Avatar asked Nov 04 '11 18:11

Tomas Mattia


2 Answers

Using Rspec 2 with Rails 3, in order to write view specs for usage of content_for, do this:

view.content_for(:main).should contain('MainHeader')
# instead of contain() I'd recommend using have_tag (webrat)
# or have_selector (capybara)

p.s. the value of a content_for(...) block by default is an empty string, so if you want to write specs showing cases in which content_for(:main) does not get called, use:

view.content_for(:main).should be_blank

Your spec could be written as:

it "should show the headers" do
  render
  view.content_for(:main).should contain('MainHeader')
  view.content_for(:side_header).should contain('SidebarHeader')
end

This way your spec shows exactly what your view does, independent of any layout. For a view spec, I think it's appropriate to test it in isolation. Is it always useful to write view specs? That's an open question.

Instead if you want to write specs showing what the markup served to the user looks like, then you'll want either a request spec or a cucumber feature. A third option would be a controller spec that includes views.

p.s. if you needed to spec a view that outputs some markup directly and delegates other markup to content_for(), you could do that this way:

it "should output 'foo' directly, not as a content_for(:other) block" do
   render
   rendered.should contain('foo')
   view.content_for(:other).should_not contain('foo')
end
it "should pass 'bar' to content_for(:other), and not output 'bar' directly" do
   render
   rendered.should_not contain('bar')
   view.content_for(:other).should contain('bar')
end

That would probably be redundant, but I just wanted to show how render() populates rendered and view.content_for. "rendered" contains whatever output the view produces directly. "view.content_for()" looks up whatever content the view delegated via content_for().

like image 143
Benissimo Avatar answered Oct 01 '22 15:10

Benissimo


From the RSpec docs:

To provide a layout for the render, you'll need to specify both the template and the layout explicitly.

I updated the spec and it passed:

require 'spec_helper'

describe 'companies/index.html.haml' do

  it 'should show the headers' do
    render :template => 'companies/index', :layout => 'layouts/companies'
    rendered.should contain('MainHeader')
    rendered.should contain('SidebarHeader')
  end

end
like image 28
Tomas Mattia Avatar answered Oct 01 '22 16:10

Tomas Mattia