David Chelimsky

random thoughtlessness

tutorial - driving view behaviour with 'spec/rails'

[Updated for 0.9 on 2007/04/12]

rspec-0.9’s ‘spec/rails’ (RSpec on Rails) plugin lets you spec your views in complete isolation of any controllers. This means that you can spec your views even before there are any models or controllers! Once models and controllers do exist, changes to them will not cause your view specs to fail.

Here’s a brief tutorial to get you started.

Create a new rails app.

rails oldnews

Now install rspec and rspec_on_rails plugins.

cd oldnews ruby script/plugin install svn://rubyforge.org/var/svn/rspec/tags/REL_0_9_0_BETA_2/rspec ruby script/plugin install svn://rubyforge.org/var/svn/rspec/tags/REL_0_9_0_BETA_2/rspec_on_rails ruby script/generate rspec

Next, using the rspec controller generator, create a controller for articles:

$ script/generate rspec_controller articles list show

    exists  app/controllers/
    exists  app/helpers/
    create  app/views/articles
    create  spec/controllers/
    create  spec/helpers/
    create  spec/views/articles
    create  spec/controllers/articles_controller_spec.rb
    create  spec/helpers/articles_helper_spec.rb
    create  app/controllers/articles_controller.rb
    create  app/helpers/articles_helper.rb
    create  spec/views/articles/list_view_spec.rb
    create  app/views/articles/list.rhtml
    create  spec/views/articles/show_view_spec.rb
    create  app/views/articles/show.rhtml

Open up spec/views/articles/list_view_spec.rb and update it as follows:

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

describe "articles/list with just one article" do
it "should display the title" do
  render 'articles/list'
  response.should have_tag('div', "example title")
end
end

Now run the specs – stand in the project root and …

ruby spec/views/articles/list_view_spec.rb -fs

… and you should get output like this (plus some backtrace info) …

articles/list with just one article – should display the title (FAILED – 1)

1) ‘articles/list with just one article should display the title’ FAILED Expected at least 1 elements, found 0. is not true.

Open up app/views/articles/list.rhtml and edit as follows…

example title

Run the spec again and you’ll see that it passes. We have duplication between the spec and the subject code. To get rid of it, we’ll have to feed the view some data that it can display. Add a setup to the spec:

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

describe "articles/list with just one article" do
before do
  @article = mock("article")
  @article.should_receive(:title).and_return("example title")
  assigns[:articles] = [@article]
end
it "should display the title" do
  render 'articles/list'
  response.should have_tag('div', "example title")
end
end

… and run the specs …

articles/list with just one article – should display the title (ERROR – 1)

1) Spec::Mocks::MockExpectationError in ‘articles/list with just one article should display the title’ Mock ‘article’ expected :title with (any args) once, but received it 0 times

This fails because the mock article never receives the :title message. You can get this to pass like so …

# in app/views/articles/list.rhtml

<%= @articles[0].title %>

So we’re part of the way there, but what we want is for the view to iterate through the list. So add another spec …

describe "articles/list with three articles" do
before do
  articles = (1..3).inject([]) do |list, index|
    article = mock("article #{index}")
    article.should_receive(:title).and_return("example title #{index}")
    list << article
  end
  assigns[:articles] = articles
end
it "should display the title" do
  render 'articles/list'
  response.should have_tag("div", "example title 1")
  response.should have_tag("div", "example title 2")
  response.should have_tag("div", "example title 3")
end
end

… which produces …

$ ruby spec/views/articles/list_view_spec.rb -fs

articles/list with just one article – should display the title

articles/list with three articles – should display the title (FAILED – 1)

1) ‘articles/list with three articles should display the title’ FAILED <“example title 2”> expected but was <“example title 1”>.

It is failing on the second title because we’re not yet iterating through the articles. So…

<% for article in @articles %>

<%= article.title %>

<% end %>

… and viola!

$ ruby spec/views/articles/list_view_spec.rb -fs

articles/list with just one article – should display the title

articles/list with three articles – should display the title

As you can see, we are able to do this without any models or actions yet developed. As those get added, this spec will continue to pass because there is no dependency between it and the real models and controllers.

Now the risk here is that you could build a model that doesn’t have a title field in it and your app will blow up! Admittedly, if you only write isolated, granular specs like this that risk is real. So you should be doing this in conjunction with integration testing. Unfortunately, RSpec 0.9 does not yet support any integration testing directly, but there are plenty of great tools that will handle that for you. If you want to keep it on the command line, use something like rails integration testing. If you like in-browser testing you can use watir or selenium.