homeASCIIcasts

155: Beginning With Cucumber 

(view original Railscast)

Other translations: Cn

Cucumber is a high-level testing framework which is designed to let you use Behaviour Driven Development (BDD) to create Ruby on Rails applications. Cucumber’s unique feature is that it uses English (or a number of other supported languages) to define an application’s behaviour. In this episode we’re going to create a Rails application from scratch, and use Cucumber to define its behaviour.

Creating The Application

We’ll start by creating a new Rails application and for the purposes of this example we’re going to use the classic example and create a blogging app. To start we’ll create the application in the usual way.

rails blog

Next we’ll have to set up the testing environment to use the gems we’ll need. At the bottom of /config/environments/test.rb we’ll add the following code.

config.gem "rspec", :lib => false, :version => ">=1.2.2"
config.gem "rspec-rails", :lib => false, :version => ">=1.2.2"
config.gem "webrat", :lib => false, :version => ">=0.4.3"
config.gem "cucumber", :lib => false, :version => ">=0.2.2"

As well as Cucumber we’re going to use RSpec and Webrat. Neither are required to use Cucumber, but they work well together. When you become more proficient with Cucumber you could use Test::Unit instead of RSpec or replace Webrat with a different testing framework.

Now that we’ve specified the gems we need we’ll make sure they’re installed by running the appropriate rake command.

sudo rake gems:install RAILS_ENV=test

A few dependent gems will be installed with the gems listed above and this may take a few minutes if you don’t already have them. Once everything is installed we can set up Cucumber for our application with

script/generate cucumber

This will generate a features directory under our application’s root directory and it’s here where we’ll define our application’s behaviour.

Creating Our First Feature

We’ll want to manage articles with our blog application, so we’ll create a file called manage_articles.feature in the features directory. (If you’re using TextMate then there’s a plug-in available that will give you syntax colouring for Cucumber’s .feature files.)

Cucumber definitions have two sections. The first is optional and is used to define the feature itself. There are three parts to this section: “In order”, “As a” and “I Want”. For our blog application we’ll define this section like this

Feature: Manage Articles
  In order to make a blog
  As an author
  I want to create and manage articles

The “In order” line defines our overall goal, which is to make a blog. The “As a” line defines a condition, and the last line states the feature we want to implement.

Now we’ll define some of the feature’s behaviour. This is done by writing one or more scenarios. A scenario is specified in a similar way to a feature, only now we have “Given”, “When” and “Then” parts. We’ll write a scenario that defines the behaviour for the page that lists the articles in our blog.

  Scenario: Articles List
    Given I have articles titled Pizza, Breadsticks
    When I go to the list of articles
    Then I should see "Pizza"
    And I should see "Breadsticks"

With that done we can now run Cucumber for the first time and see the output it returns. We’re using the -n switch to make Cucmumber return slightly more concise output.

$ cucumber features -n
Feature: Manage Articles
	In order to make a blog
	As an author
	I want to create and manage articles

  Scenario: Articles List
    Given I have articles titled Pizza, Breadsticks
    When I go to the list of articles
    Then I should see "Pizza"
    And I should see "Breadsticks"

1 scenario
3 skipped steps
1 undefined step

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

Given /^I have articles titled Pizza, Breadsticks$/ do
  pending
end

Cucumber shows us our feature and the results of running the scenario. Three of the steps were skipped and one is marked as undefined. Cucumber doesn’t know how to run the undefined step as we’ve not yet written the Ruby code to implement it. To start us off Cucumber helpfully provides a code template that we can paste in.

Defining The “Given” Step

Steps are defined in step_definitions under the features directory. We’ll create a file called article_steps.rb in this directory so that we can start to define our scenario.

Given /^I have articles titled Pizza, Breadsticks$/ do
  pending
end

Our step is defined as a regular expression. This will interpret the plain English in the scenario and turn it into something that we can use in Ruby code. It’s a good idea to make a step work with any data passed to it so we’ll replace “Pizza, Breadsticks” in the regular expression with a sub-expression and then matching each title that the sub-expression returns.

Given /^I have articles titled (.+)$/ do |titles|
  titles.split(', ').each do |title|
    Article.create!(:title => title)
  end
end

We’re going to pass a list of titles separated by a comma and a space and that list will be matched by the (.+) part of the regular expression and passed to the titles variable. We then split that string at “, “ to get each title. With each title matched we then create a new Article with that title. This will satisfy our Given condition.

What About The Other Steps?

When we ran Cucumber it told us that one step was undefined and skipped the other three. This means that although they weren’t executed, the step definitions do exist. If this is so then where are they defined? The answer is that Webrat defines a set of steps in a file called webrat_steps.rb in the same directory as our article steps. These steps define a list of common tasks, such as going to a page.

When /^I go to (.+)$/ do |page_name|
  visit path_to(page_name)
end

This step definition matches our second step (When I go to the list of articles) and there are step definitions to match the other skipped steps, too.

Making It Turn Green, Step By Step

Now that all of our steps are defined we can run Cucumber again and check the output to see what we need to do next.

$ cucumber features -n
Feature: Manage Articles
	In order to make a blog
	As an author
	I want to create and manage articles

  Scenario: Articles List
    Given I have articles titled Pizza, Breadsticks
      uninitialized constant Article (NameError)
      ./features/step_definitions/article_steps.rb:3:in `__instance_exec0'
      ./features/step_definitions/article_steps.rb:2:in `each'
      ./features/step_definitions/article_steps.rb:2:in `/^I have articles titled (.+)$/'
      features/manage_articles.feature:7:in `Given I have articles titled Pizza, Breadsticks'
    When I go to the list of articles
    Then I should see "Pizza"
    And I should see "Breadsticks"

1 scenario
1 failed step
3 skipped steps

This time instead of an undefined step we have a failing step. The step fails because we don’t have an Article model. To fix this we’ll generate it and, as we’re using RSpec, we’ll use the RSpec generator to create it.

script/generate rspec_model Article title:string content:text

That done we’ll run the migrations and clone the schema changes to the test database.

rake db:migrate
rake db:test:clone

Now we can run Cucumber again.

$ cucumber features -n
Feature: Manage Articles
  In order to make a blog
  As an author
  I want to create and manage articles

  Scenario: Articles List
    Given I have articles titled Pizza, Breadsticks
    When I go to the list of articles
      Can't find mapping from "the list of articles" to a path.
      Now, go and add a mapping in features/support/paths.rb (RuntimeError)
      /Users/eifion/rails/apps_for_asciicasts/blog/features/support/paths.rb:12:in `path_to'
      ./features/step_definitions/webrat_steps.rb:11:in `/^I go to (.+)$/'
      features/manage_articles.feature:8:in `When I go to the list of articles'
    Then I should see "Pizza"
    And I should see "Breadsticks"

1 scenario
1 failed step
2 skipped steps
1 passed step

We have our first passing step! The second step is now failing, though. This is because Cucumber doesn’t know how to translate “the list of articles” into a path in our application.

One of the files installed by Cucumber is called paths.rb, which lives in the /features/support directory. In this file we can define custom paths that map the English definitions in Cucumber files to paths in our Rails application. To add a mapping we just add a new when condition to the case statement in the path_to method. (There’s a comment in the file to show us where to add it.)

# Add more page name => path mappings here
when /the list of articles/
  articles_path

Running Cucumber again will show the next error that needs fixing. This time it cannot find a route that matches articles_path. We just need to add the mapping to routes.rb.

map.resources :articles

That done, Cucumber will now tell us that the ArticlesController is missing. As with the model we’ll use the RSpec generator to create it.

script/generate rspec_controller articles index

Cucumber still isn’t happy though. It expects to see “Pizza” in the output from the view, but as we’ve not done anything to the index view yet it won’t find it. We’ll modify the controller’s index action and the view code to show the list of articles.

def index
  @articles = Article.all
end

The index action in the ArticlesController.

<h1>Articles</h1>
<% @articles.each do |article| %>
  <h2><%= h(article.title) %></h2>
  <p><%= h(article.content) %></p>
<% end %>

The view code for the Articles index page.

This time Cucumber is happy and shows four passing steps when we run it.

$ cucumber features -n
Feature: Manage Articles
	In order to make a blog
	As an author
	I want to create and manage articles

  Scenario: Articles List
    Given I have articles titled Pizza, Breadsticks
    When I go to the list of articles
    Then I should see "Pizza"
    And I should see "Breadsticks"

1 scenario
4 passed steps

It looks like we’ve had to put a lot of effort in to get this one Cucumber scenario to pass, but we have been taking deliberately small steps to show how Cucumber always tells you what you need to do next. As you get more comfortable with Cucumber you can take bigger steps between each Cucumber run.

Another Scenario

Now that we’ve shown an overview of how Cucumber works we’ll work through another, more complex scenario, but we’ll take bigger steps.

Scenario: Create Valid Article
  Given I have no articles
  And I am on the list of articles
  When I follow "New Article"
  And I fill in "Title" with "Spuds"
  And I fill in "Content" with "Delicious potato wedges!"
  And I press "Create"
  Then I should see "New article created."
  And I should see "Spuds"
  And I should see "Delicious potato wedges!"
  And I should have 1 article

This scenario defines the behaviour for creating a new article but unlike the last one it spans several pages of our application. The scenario assumes that we have no articles to start with, clicks a link with the text “New Article”, fills in a form and submits it then expects to see the new article and find one article in the database.

As always our first step is to run Cucumber. When we do it will tell us that we have two undefined steps and six skipped ones. The two undefined steps are the first (“Given I have no articles”) and the last (“And I should have 1 article”). The other six are covered by Webrat steps.

The first thing to do is to create the two undefined steps.

Given /^I have no articles$/ do
  Article.delete_all
end

Then /^I should have ([0-9]+) article$/ do |count|
  Article.count.should == count.to_i
end

The first step is straightforward. To satisfy the condition we just delete all the articles. For the second step we have replaced the number 1 with a regular expression that matches any positive number and passed that number as a variable to the block. The block then checks that the number of articles equals the variable that was passed. Note that Cucumber will treat all variables matched in a regular expression as strings so we have to convert the count to an integer.

With those done another Cucumber run shows us that the first two steps now pass, but the third one fails.

 
Given I have no articles
And I am on the list of articles
When I follow "New Article"
  Could not find link with text or title or id "New Article" (Webrat::NotFoundError)
  (eval):2:in `click_link'
  ./features/step_definitions/webrat_steps.rb:19:in `/^I follow "([^\"]*)"$/'
  features/manage_articles.feature:15:in `When I follow "New Article"'

The step fails because Cucumber could not find a link with the text “New Article”. To make the step pass we’ll need to create link from the index page to the new article page and create the page to create a new article.

On the index page we’ll just add a link to the new article page.

<p><%= link_to "New Article", new_article_path %></p>

And then create the page for the new article with a form on it.

<% form_for @article do |f| %>
  <ol class="formList">
    <li>
      <%= f.label :title %>
      <%= f.text_field :title %>
    </li>
    <li>
      <%= f.label :content %>
      <%= f.text_field :content %>
    </li>
    <li>
      <%= f.submit "Create" %>
    </li>
  </ol>
<% end %>

Finally we’ll set up the controllers.

def new
  @article = Article.new
end
  
def create
  @article = Article.create!(params[:article])
  flash[:notice] = "New article created."
  redirect_to articles_path
end

We’ve taken quite a big step here and implemented the new and create actions for our controller. We’ll give Cucumber another run through the scenario and see what needs to be done next. This time the first six steps pass, but then we get this failing step.

Then I should see "New article created."
  expected the following element's content to include "New article created.":
  ArticlesSpudsDelicious potato wedges!New Article (Spec::Expectations::ExpectationNotMetError)
  ./features/step_definitions/webrat_steps.rb:94:in `/^I should see "([^\"]*)"$/'
  features/manage_articles.feature:19:in `Then I should see "New article created."'

Although we’ve created the flash message in the create action we’re not displaying it on the index page. Adding it in should make this step pass. It should go in a layout file, but we’ll just add it to the top of the index page to make the step pass.

<%= flash[:notice] %>

Everything’s Gone Green

With the flash message added to the index page when we run Cucumber again all of the steps pass.

$ cucumber features -n
Feature: Manage Articles
	In order to make a blog
	As an author
	I want to create and manage articles

  Scenario: Articles List
    Given I have articles titled Pizza, Breadsticks
    When I go to the list of articles
    Then I should see "Pizza"
    And I should see "Breadsticks"

  Scenario: Create Valid Article
    Given I have no articles
    And I am on the list of articles
    When I follow "New Article"
    And I fill in "Title" with "Spuds"
    And I fill in "Content" with "Delicious potato wedges!"
    And I press "Create"
    Then I should see "New article created."
    And I should see "Spuds"
    And I should see "Delicious potato wedges!"
    And I should have 1 article

2 scenarios
14 passed steps

Our application now behaves as we want it to. With the scenario passing the next steps would be to tidy up the code, check that everything still passes and then write the next scenario, say one for an invalid article.

We’ve only really covered the basics of Cucumber here. It might seem like quite a lot of work to define the tests in English and implement each step one at a time, but Cucumber testing has several advantages. With it you’re testing your whole stack, so you’re writing tests that cover everything from the user interface down to the database. While they’re not a replacement for unit tests, they provide an excellent way of writing high-level tests to test the overall behaviour of your application.