homeASCIIcasts

257: Request Specs and Capybara 

(view original Railscast)

Other translations: Ja Es

High-level testing is a great way to test Rails applications. A popular way to perform this kind of testing is Cucumber which was covered here back in episode 155 [watch, read]. Not everyone likes Cucumber’s English-style syntax for defining the application’s behaviour, though, so this episode we’ll take a look at an alternative way to do high-level testing.

While we’d normally advocate test-driven development, in this episode we’ll be adding tests to an existing application so that we don’t have to worry about the implementation details and making the application work. The application we’ll be adding tests to is a simple, one-page to-do list application. This app shows a list of tasks and has a form that allows new tasks to be added.

Our simple task list application.

To test this application we’re going to use request specs, which are available in RSpec 2.0, so the first step we’ll need to take is to add the RSpec Rails gem to the development and test groups in the Gemfile and then run bundle.

/Gemfile

source 'http://rubygems.org'

gem 'rails', '3.0.5'
gem 'sqlite3'
gem 'nifty-generators'

group :development, :test do
  gem 'rspec-rails'
end

Once Bundler has run we can run the following command to set up RSpec in our application.

$ rails g rspec:install

Request specs are RSpec’s equivalent to Rails’ built-in integration testing (these were covered in episode 187 [watch, read]). To generate a request spec we call the integration test generator.

$ rails g integration_test task

This will create a tasks_spec.rb file in the spec/requests directory, which looks like this:

/spec/requests/tasks_spec.rb

require 'spec_helper'

describe "Tasks" do
  describe "GET /tasks" do
    it "works! (now write some real specs)" do
      # Run the generator again with the --webrat flag if you want to use webrat methods/matchers
      get tasks_path
      response.status.should be(200)
    end
  end
end

The contents of this file look like fairly standard RSpec tests with the difference that we can call methods like get to call a page in the application and response to check the response for that request. The spec that’s included by default in the file requests the tasks page that we saw earlier and checks that the response status is 200, indicating a successful response. This spec should pass without modification, so let’s try it. We can run rake spec:requests to run only the request specs.

$ rake spec:requests
(in /Users/eifion/code/tasklist)
/Users/eifion/.rvm/rubies/ruby-1.9.2-p0/bin/ruby -S bundle exec rspec ./spec/requests/tasks_spec.rb
DEPRECATION WARNING: <% %> style block helpers are deprecated. Please use <%= %>. (called from _app_views_tasks_index_html_erb___875755388255758006_2152410020_3563250333774836596 at /Users/eifion/code/tasklist/app/views/tasks/index.html.erb:3)
.

Finished in 0.18535 seconds
1 example, 0 failures

The spec passes, but we do get a deprecation warning and this is the kind of issue that this kind of testing can find but which would be missed if we were just testing directly in a browser. It looks like we forgot to use an equals sign in a block helper and so we have some useful feedback already from writing request specs.

In the view file for the form we’ll see that we’ve missed the equals sign from the opening form_for tag which is required in Rails 3.

/app/views/tasks/index.html.erb

<% form_for Task.new do |f| %>

If we add this in…

/app/views/tasks/index.html.erb

<%= form_for Task.new do |f| %>

…and then run the specs again we’ll get a passing spec and no warnings.

$ rake spec:requests
(in /Users/eifion/code/tasklist)
/Users/eifion/.rvm/rubies/ruby-1.9.2-p0/bin/ruby -S bundle exec rspec ./spec/requests/tasks_spec.rb
.

Finished in 0.16725 seconds
1 example, 0 failures

Our First Real Request Spec

In a request spec we generally want to do more than just test the status of the response. We’ll replace the default spec now with one that tests that the tasks are shown on the page.

/specs/requests/tasks_spec.rb

require 'spec_helper'

describe "Tasks" do
  describe "GET /tasks" do
    it "displays tasks" do
      Task.create!(:name => "paint fence")
      get tasks_path
      response.body.should include("paint fence")
    end
  end
end

This spec is pretty straightforward. We create a new task then visit the tasks page and check that the text on the page includes that task’s name. When we run rake spec:requests the spec passes as the page does include that text.

As requests specs are built on Rails integration tests they support all of the methods that integration tests have. For example if we want to test the creation of a new task we can make use of the post_via_redirect method to make sure that the redirect is followed when we create a task.

Let’s write that spec now. It will call post_via_redirect to POST to the index page and pass in the parameters to create a new task called “mow lawn”. It then checks that that text is present in the resulting page.

/specs/requests/tasks_spec.rb

require 'spec_helper'

describe "Tasks" do
  
  # Other task omitted.
  
  describe "POST /tasks" do
    it "creates a task" do
      post_via_redirect tasks_path, ↵
	   :task => { :name => "mow lawn" }
      response.body.should include("mow lawn")
    end
  end
end

If we run the specs now we’ll have two passing specs, so it looks like our code works as expected. If we were doing test-driven development we’d start with a failing spec and then write code to make it pass and this is one of the advantages of test-driven development as it ensures that your tests are working. In our application we’ve jumped straight to a passing test as the implementation is already in place. When adding tests for existing code it can sometimes be worth deliberately breaking something in the code to ensure that the test then fails and then fixing it again.

Testing The UI With Capybara

One problem with our request spec is that it doesn’t test the full user experience. We could completely break the New Task form and this wouldn’t be caught in the specs. This is because we’re POSTing directly to the create action instead of going through the form like the user would.

We need a way to mimic the user’s actions and we can do this by using Capybara. This is an alternative to Webrat, which was covered back in episode 156 [watch, read]. Capybara gives us a number of methods we can use to mimic a user’s behaviour in a web application. Capybara is a gem and is installed in the usual way. We’ll also add the launchy gem and you’ll see why shortly. In the Gemfile we’ll add both gems to the development and test groups and then run bundle to install it.

/Gemfile

source 'http://rubygems.org'

gem 'rails', '3.0.5'
gem 'sqlite3'
gem 'nifty-generators'

group :development, :test do
  gem 'rspec-rails'
  gem 'capybara'
  gem 'launchy'
end

Capybara is automatically included in our request specs so in the first of our specs we can use Capybara’s visit method instead of calling get. We can also replace response.body.should include with page.should have_content.

In the second spec we can now use Capybara to simulate filling in and submitting the form instead of POSTing directly to the create action. The fill_in method will find the text box with the associated label “Name” and set its value and we can use click_button to find the form’s button by its text and click it.

/specs/requests/task_spec.rb

require 'spec_helper'

describe "Tasks" do
  describe "GET /tasks" do
    it "displays tasks" do
      Task.create!(:name => "paint fence")
      visit tasks_path
      page.should have_content("paint fence")
    end
  end
  
  describe "POST /tasks" do
    it "creates a task" do
      visit tasks_path
      fill_in "Name", :with => "mow lawn"
      click_button "Add"
	 page.should have_content("Successfully added task.")
      page.should have_content("mow lawn")
    end
  end
end

The specs still pass when we run them and now test the form too so if we make any breaking changes to the form this will be picked up.

Debugging Pages

If a spec does fail how can be go about debugging that problem? This is where launchy comes in. As we’re using Capybara we can call the save_and_open_page method at any point and this will open the page in a browser so that we can take a look at it. We’ll add it immediately after click_button "Add" and when we run the specs again we’ll see the tasks page just before we check its contents.

The page as it is part way through the spec.

We can see the state of the page at this point with the flash notice visible and the task that was added through Capybara.

Testing JavaScript

Our application is now pretty well tested and these tests have been simple to add with request specs and Capybara. But what if we have JavaScript in our application and want to test that? This can be done quite easily and we’ll show you how now.

In our index template we’ll add some JavaScript so that we can test it. We’ll keep it inline for simplicity’s sake and use link_to_function to add a link to the page that calls a JavaScript function when clicked that will change the link’s text to "js works". The script that runs requires jQuery so we’ve added a reference to it in the application’s layout file. (By the way, jQuery will be the default JavaScript library in Rails 3.1 instead of Prototype).

/app/views/tasks/index.html.erb

<h1>Task List</h1>

<%= link_to_function "test js", '$(this).html("js works")' %>

<%= form_for Task.new do |f| %>
  <p>
    <%= f.label :name %>
    <%= f.text_field :name %>
    <%= f.submit "Add" %>
  </p>
<% end %>

<ul>
  <% @tasks.each do |task| %>
    <li><%= task.name %></li>
  <% end %>
</ul>

So now we have a link on the page that says "test js" and when we click it the text changes.

The changed text after clicking the JavaScript link.

Let’s test this new functionality through Capybara. First we’ll write a new spec:

/spec/requests/tasks_spec.rb

it "supports js" do
  visit_tasks_path
  click_link "test js"
  page.should have_content("js works")
end

When we run the request specs now we get one failure.

$ rake spec:requests
(in /Users/eifion/code/tasklist)
/Users/eifion/.rvm/rubies/ruby-1.9.2-p0/bin/ruby -S bundle exec rspec ./spec/requests/tasks_spec.rb
.F.

Failures:

  1) Tasks GET /tasks supports js
     Failure/Error: page.should have_content("js works")
       expected #has_content?("js works") to return true, got false
     # ./spec/requests/tasks_spec.rb:14:in `block (3 levels) in <top (required)>'

Finished in 0.83232 seconds
3 examples, 1 failure
rake aborted!
ruby -S bundle exec rspec ./spec/requests/tasks_spec.rb failed

(See full trace by running task with --trace)

The spec fails because the page doesn’t have the text that is set by JavaScript. Capybara doesn’t support JavaScript by default. We have to tell it to use JavaScript through Selenium.

The functionality we’re going to show now isn’t supported in the current release version of Capybara. Bundler makes it easy, however, to get the latest version from GitHub.

/Gemfile

source 'http://rubygems.org'

gem 'rails', '3.0.5'
gem 'sqlite3'
gem 'nifty-generators'

group :development, :test do
  gem 'rspec-rails'
  gem 'capybara', :git => 'git://github.com/jnicklas/capybara.git'
  gem 'launchy'
end

When we run bundle again the latest version of the Capybara gem will be downloaded and installed.

Another step that’s required to get JavaScript testing working is to modify the spec_helper file and add the line require 'rspec/rails'.

/spec/spec_helper.rb

# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'capybara/rspec'

# rest of file...

This step might not be required in the release version but for now it is. It’s easy now to tell Capybara to use a JavaScript driver for any of our specs. All we have to do is add the :js => true option to the spec.

/spec/requests/tasks_spec.rb

it "supports js", :js => true do
  visit tasks_path
  click_link "test js"
  page.should have_content("js works")
end

When we run the specs now Firefox will open to run the specs that require JavaScript. All of the specs pass now pass again.

This is a great feature to have but there is a problem to do with database records here that we’ll probably run into. We can demonstrate this by adding the :js => true option to the first two specs, including the one that creates a new Task.

/spec/requests/tasks_spec.rb

describe "Tasks" do
  describe "GET /tasks", :js => true do
    it "displays tasks" do
      Task.create!(:name => "paint fence")
      visit tasks_path
      page.should have_content("paint fence")
    end
    
    it "supports js" do
      visit tasks_path
      click_link "test js"
      page.should have_content("js works")
    end    
  end
end

When we run the specs now it’ll use Selenium for the first two requests.

$ rake spec:requests
(in /Users/eifion/code/tasklist)
/Users/eifion/.rvm/rubies/ruby-1.9.2-p0/bin/ruby -S bundle exec rspec ./spec/requests/tasks_spec.rb
F..

Failures:

  1) Tasks GET /tasks displays tasks
     Failure/Error: page.should have_content("paint fence")
       expected there to be content "paint fence" in "Task List\ntest js\n\nName"
     # ./spec/requests/tasks_spec.rb:8:in `block (3 levels) in <top (required)>'

Finished in 7.69 seconds
3 examples, 1 failure

This time the first spec fails as the content “paint fence” is missing from the page. The database record that is created isn’t available to the Selenium tests and this is because our specs are using database transactions which aren’t compatible with Selenium. To fix this we can set the config.use_transactional_fixtures setting in the spec_helper file to false.

/spec/spec_helper.rb

# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
# instead of true.
config.use_transactional_fixtures = false

This will get the tests passing again but it will mean that the database records are carried over between specs which we don’t want. To solve this problem we can use a gem called database_cleaner to clean the database between specs. The documentation explains the options we have.

To use it we’ll add a reference in the Gemfile and run bundle again to install it.

/Gemfile

source 'http://rubygems.org'

gem 'rails', '3.0.5'
gem 'sqlite3'
gem 'nifty-generators'

group :development, :test do
  gem 'rspec-rails'
  gem 'capybara', :git => 'git://github.com/jnicklas/capybara.git'
  gem 'launchy'
  gem 'database_cleaner'
end

Next we’ll modify the spec_helper file again and add the code to clean the database between specs.

/spec/spec_helper.rb
config.before(:suite) do
  DatabaseCleaner.strategy = :truncation
end

config.before(:each) do
  DatabaseCleaner.start
end

config.after(:each) do
  DatabaseCleaner.clean
end

When we run the specs now, with the first two running through Selenium again, they all pass as we’re no longer using database transactions. This might have seemed like a lot of work, but once it’s all set up it’s easy to test JavaScript for any spec by just adding the :js => true option.

A feature that will be in the next Capybara release that we haven’t covered is a new DSL for defining specs. We’ll be able to use feature, background and scenario methods to define our specs in a similar way to how the Steak gem works. If you like working with that kind of DSL it’ll be built into Capybara which eliminates the need to use Steak.