Getting started with Cucumber, RSpec, Webrat and multiruby

After a couple years off from full-time Ruby/Rails work, I’m getting back into it having just joined the development team at Songkick. Much as I’ve tried to keep my hand in with the Ruby world by hacking on stuff like Heist, Siren and Jake, a few things have passed me by. In particular, I’ve not been doing any big Rails apps for a while so I’m a little behind on the current testing toolset. Songkick uses Cucumber for acceptance testing, with help from RSpec, Webrat and factory_girl. The test suite is huge, so needless to say I’ve spent a lot of time getting up to speed on these tools so I can get to work.

While these tools have great support for various web frameworks, I wanted to get down to basics so I can try them out for non-Rails (and non-web) projects. I’m going to go through all the setup you need to start writing Cucumber features by writing a quick test for checking Google search results.

First up, you’re going to need a bunch of gems. Webrat uses Nokogiri for parsing HTML documents, and we need a couple of Ubuntu packages to support that:

$ sudo aptitude install libxslt1-dev libxml2-dev

Then you need to install the required gems. You’ll need to get multiruby set up, then run these:

$ sudo gem install cucumber rspec webrat mechanize
$ multiruby -S gem install cucumber rspec webrat mechanize

Now we have the libraries we need, we have to set up some directories that Cucumber expects to find things in:

$ mkdir features
$ mkdir features/step_definitions
$ mkdir features/support

We can now run Cucumber and it will tell us we’ve got no tests:

$ cucumber
0 scenarios
0 steps
0m0.000s

Let’s get testing. We start by writing features in Cucumber’s plain-text language to describe what we want our app to do. It’s common to put some text at the start of a feature to explain why it’s being added. Save the following as features/search.feature:

Feature: Search Google
  In order to make sure people can find my documentation
  I want to check it is listed on the first page in Google
  
  Scenario: Searching for JS.Class docs
    Given I have opened "http://www.google.com/"
    When I search for "JS.Class"
    Then I should see a link to "http://jsclass.jcoglan.com/" with text "JS.Class - Ruby-style JavaScript"

Running Cucumber again will tell us we’ve not defined the steps: we’ve not said what any of the above text means:

$ cucumber
Feature: Search Google
  In order to make sure people can find my documentation
  I want to check it is listed on the first page in Google

  Scenario: Searching for JS.Class docs                                                                    # features/search.feature:5
    Given I have opened "http://www.google.com/"                                                           # features/search.feature:6
    When I search for "JS.Class"                                                                           # features/search.feature:7
    Then I should see a link to "http://jsclass.jcoglan.com/" with text "JS.Class - Ruby-style JavaScript" # features/search.feature:8

1 scenario (1 undefined)
3 steps (3 undefined)
0m0.002s

You can implement step definitions for undefined steps with these snippets:

Given /^I have opened "([^\"]*)"$/ do |arg1|
  pending
end

When /^I search for "([^\"]*)"$/ do |arg1|
  pending
end

Then /^I should see a link to "([^\"]*)" with text "([^\"]*)"$/ do |arg1, arg2|
  pending
end

We have to fix these by adding step definitions to step_definitions/search_steps.rb: copy the above definitions into the file then run Cucumber:

$ cucumber
Feature: Search Google
  In order to make sure people can find my documentation
  I want to check it is listed on the first page in Google

  Scenario: Searching for JS.Class docs                                                                    # features/search.feature:5
    Given I have opened "http://www.google.com/"                                                           # features/step_definitions/search_steps.rb:1
      TODO (Cucumber::Pending)
      features/search.feature:6:in `Given I have opened "http://www.google.com/"'
    When I search for "JS.Class"                                                                           # features/step_definitions/search_steps.rb:5
    Then I should see a link to "http://jsclass.jcoglan.com/" with text "JS.Class - Ruby-style JavaScript" # features/step_definitions/search_steps.rb:9

1 scenario (1 pending)
3 steps (2 skipped, 1 pending)
0m0.002s

The first step will be marked yellow to indicate that it’s pending, and the remaining steps will be blue to show they’ve been skipped. Let’s try to turn the first step green. In step_definitions/search_steps.rb, change the first definition to the following:

Given /^I have opened "([^\"]*)"$/ do |url|
  visit url
end

visit is a method provided by Webrat for getting web pages. We need to tell Cucumber to load Webrat and include its methods in the testing environment, which we do by putting this in support/env.rb:

require 'webrat'

Webrat.configure do |config|
  config.mode = :mechanize
end

World(Webrat::Methods)
World(Webrat::Matchers)

Webrat doesn’t actually make requests itself, it provides adapters to other systems such as Rails and Sinatra for calling their web stacks with a uniform API. We just want to query the web, so we’re using telling Webrat to use Mechanize to fetch pages. We then mix two modules into the Cucumber World, the context that all your steps run in. Webrat::Methods provides methods like visit and click_button for navigating the web, and Webrat::Matchers provides things like have_selector and contains for use with RSpec’s should interface.

Running our features now gives the following:

 $ cucumber
Feature: Search Google
  In order to make sure people can find my documentation
  I want to check it is listed on the first page in Google

  Scenario: Searching for JS.Class docs                                                                    # features/search.feature:5
    Given I have opened "http://www.google.com/"                                                           # features/step_definitions/search_steps.rb:1
    When I search for "JS.Class"                                                                           # features/step_definitions/search_steps.rb:5
      TODO (Cucumber::Pending)
      features/search.feature:7:in `When I search for "JS.Class"'
    Then I should see a link to "http://jsclass.jcoglan.com/" with text "JS.Class - Ruby-style JavaScript" # features/step_definitions/search_steps.rb:9

1 scenario (1 pending)
3 steps (1 skipped, 1 pending, 1 passed)
0m0.624s

Great, one passing step! Let’s move onto the next one: we want to tell Webrat to fill in Google’s search form and submit. Replace the second step with:

When /^I search for "([^\"]*)"$/ do |query|
  fill_in "q", :with => query
  click_button "Google Search"
end

We’ll be able to access the HTTP response after the form submission using Webrat’s response_body method in the next step. Run the features again:

$ cucumber
Feature: Search Google
  In order to make sure people can find my documentation
  I want to check it is listed on the first page in Google

  Scenario: Searching for JS.Class docs                                                                    # features/search.feature:5
    Given I have opened "http://www.google.com/"                                                           # features/step_definitions/search_steps.rb:1
    When I search for "JS.Class"                                                                           # features/step_definitions/search_steps.rb:5
    Then I should see a link to "http://jsclass.jcoglan.com/" with text "JS.Class - Ruby-style JavaScript" # features/step_definitions/search_steps.rb:10
      TODO (Cucumber::Pending)
      features/search.feature:8:in `Then I should see a link to "http://jsclass.jcoglan.com/" with text "JS.Class - Ruby-style JavaScript"'

1 scenario (1 pending)
3 steps (1 pending, 2 passed)
0m0.481s

Only one step left. In the Then step, we want to use RSpec to check some condition is true. We want to check that a link exists with the URL we’ve given, and that link should contain the given text. Webrat uses Nokogiri to let you query the response using CSS selectors:

Then /^I should see a link to "([^\"]*)" with text "([^\"]*)"$/ do |url, text|
  response_body.should have_selector("a[href='#{ url }']") do |element|
    element.should contain(text)
  end
end

One more test run:

$ cucumber
Feature: Search Google
  In order to make sure people can find my documentation
  I want to check it is listed on the first page in Google

  Scenario: Searching for JS.Class docs                                                                    # features/search.feature:5
    Given I have opened "http://www.google.com/"                                                           # features/step_definitions/search_steps.rb:1
    When I search for "JS.Class"                                                                           # features/step_definitions/search_steps.rb:5
    Then I should see a link to "http://jsclass.jcoglan.com/" with text "JS.Class - Ruby-style JavaScript" # features/step_definitions/search_steps.rb:10

1 scenario (1 passed)
3 steps (3 passed)
0m0.537s

And a final check with multiruby:

$ multiruby -S cucumber

TOTAL RESULT = 0 failures out of 3

Passed: 1.9.1-p129, 1.8.7-p160, 1.8.6-p368
Failed:

Hopefully that’s enough of an introduction to get you started: the main problem I had was figuring out which libraries were providing which testing features and what setup was required. There’s a ton of helpful information in the Cucumber wiki to move beyond the basics.

If you've enjoyed this article, you might enjoy my recently published book JavaScript Testing Recipes. It's full of simple techniques for writing modular, maintainable JavaScript apps in the browser and on the server.