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.