Rails 3.1 and Cucumber: getting started with outside-in testing

Updated February 19, 2012: Entire blog post proof checked and web_steps.rb code removed in favor of custom steps, as advocated by the Cucumber developers.

October 7, 2011: Cucumber’s web_steps.rb file is going away. At the moment, this article leverages web_steps.rb, but an update and extension is in the works. In the meantime, you can use this with an older version of Cucumber without difficulty.

Updated June 8, 2011: This article should work with Rails 3.1 with Ruby 1.9.3-head.

Cucumber is a great weapon in the arsenal of any web developer.

Unfortunately, in mid-November 2010, most of the documentation for setting up Cucumber with Rails is for Rails 2 instead of Rails 3.

This article helps fix a little bit of that…

This article and code is a from-scratch re-implementation of Sarah Mei’s Outside In BDD: How? updated for Rails 3.

But there are some differences. Here, we start with a bare Rails application. The only generators we’re going to use are for installing Cucumber. Then, we’ll drive the development one file and one method at a time. You will see lots of familiar error messages, along with exactly how those errors were fixed.

Here, we have a user adding a new book title to a list of book titles. That’s all the information necessary to build out and test with Cucumber.

Setting it up

First up, create your new Rails code:

$ rails new outsidein
$ cd outsidein
$ rm public/index.html

Add cucumber-rails and database_cleaner to :development and :test groups in your Gemfile:

source 'http://rubygems.org'

gem 'rails', '3.1.0.rc2'
gem 'sqlite3'

# Asset template engines
gem 'sass-rails', "~> 3.1.0.rc"
gem 'coffee-script'
gem 'uglifier'

gem 'jquery-rails'

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

As usual, run bundler:

$ bundle install
Fetching source index for http://rubygems.org/
Using rake (0.9.2) 
Using multi_json (1.0.3) 
.
.
.
Using turn (0.8.2) 
Using uglifier (0.5.4) 
Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.
$

Set up Cucumber

$ rails generate cucumber:install
      create  config/cucumber.yml
      create  script/cucumber
       chmod  script/cucumber
      create  features/step_definitions
      create  features/step_definitions/web_steps.rb
      create  features/support
      create  features/support/paths.rb
      create  features/support/selectors.rb
      create  features/support/env.rb
       exist  lib/tasks
      create  lib/tasks/cucumber.rake
         gsub  config/database.yml
         gsub  config/database.yml
       force  config/database.yml
$

At this point, we’re about ready to write our application.

Step 1: Given I go to the new book page

Let’s create our first feature, features/book.feature:

Feature: User manages books
  Scenario: User adds a new book
    Given I go to the new book page
    And I fill in "Name" with "War & Peace"
    When I press "Create" 
    Then I should be on the book list page
    And I should see "War & Peace"
$ cucumber
We have no steps.
Solution: Add file features/step_definitions/book_steps.rb, and copy in the output:

Given /^I go to the new book page$/ do
  pending # express the regexp above with the code you wish you had
end

Given /^I fill in "([^"]*)" with "([^"]*)"$/ do |arg1, arg2|
  pending # express the regexp above with the code you wish you had
end

When /^I press "([^"]*)"$/ do |arg1|
  pending # express the regexp above with the code you wish you had
end

Then /^I should be on the book list page$/ do
  pending # express the regexp above with the code you wish you had
end

Then /^I should see "([^"]*)"$/ do |arg1|
  pending # express the regexp above with the code you wish you had
end
$ cucumber
Using the default profile...
Feature: User manages books

  Scenario: User adds a new book            # features/book.feature:2
Deprecated: please use #source_tags instead.
    Given I go to the new book page         # features/step_definitions/book_steps.rb:1
      TODO (Cucumber::Pending)
      ./features/step_definitions/book_steps.rb:2:in `/^I go to the new book page$/'
      features/book.feature:3:in `Given I go to the new book page'
    And I fill in "Name" with "War & Peace" # features/step_definitions/book_steps.rb:5
    When I press "Create"                   # features/step_definitions/book_steps.rb:9
    Then I should be on the book list page  # features/step_definitions/book_steps.rb:13
    And I should see "War & Peace"          # features/step_definitions/book_steps.rb:17

1 scenario (1 pending)
5 steps (4 skipped, 1 pending)
0m1.003s

Solution:

Given /^I go to the new book page$/ do
  visit new_book_path
end
$ cucumber
We’re failing at the first step of the scenario: undefined local variable or method `new_book_path' for # (NameError).
Solution: Add resources :books to config/routes.rb.

While we’re at it, go ahead and add a root path, this will be helpful later: root :to => 'books#index'.

$ cucumber
Failing again: uninitialized constant BooksController (ActionController::RoutingError)
Solution: Add the controller file app/controllers/books_controller.rb

class BooksController < ApplicationController
end
$ cucumber
Failing again: The action 'new' could not be found for BooksController (AbstractController::ActionNotFound)
Solution: Add the new method:

class BooksController < ApplicationController
  def new
  end
end
$ cucumber
Yikes! Missing template books/new with {:handlers=>[:erb, :rjs, :builder, :rhtml, :rxml], :formats=>[:html], :locale=>[:en, :en]} in view paths "/Users/daviddoolin/src/bdd/app/views" (ActionView::MissingTemplate)

$ mkdir app/views/books
$ vi app/views/books/new.html.erb

Just stick an h2 in that file or something.

$ cucumber
Passed!

One down, four to go.

Step 2: And I fill in "Name" with "War & Peace"

cucumber now fails on the second step:

$ cucumber
    And I fill in "Name" with "War & Peace" # features/step_definitions/book_steps.rb:5
      TODO (Cucumber::Pending)
      ./features/step_definitions/book_steps.rb:6:in `/^I fill in "([^"]*)" with "([^"]*)"$/'
      features/book.feature:4:in `And I fill in "Name" with "War & Peace"'

Solution: add the Capybara `fill_in` matcher:

Given /^I fill in "([^"]*)" with "([^"]*)"$/ do |arg1, arg2|
  fill_in(arg1, :with => arg2)
end
$ cucumber
Fails: cannot fill in, no text field, text area or password field with id, name, or label 'Name' found (Capybara::ElementNotFound)
Solution: Add a form to the new book page:

<%= form_for @book do |f| %>
  <%= f.label :name %>
  <%= f.text_field :name %>
  <%= f.submit 'Create' %>
<% end %>
$ cucumber
Massive FAIL! undefined method `model_name' for NilClass:Class (ActionView::Template::Error)
Solution: Add instance variable to make Rails happy. In app/controllers/books_controller.rb, add @book = Book.new, like so:

  def new
    @book = Book.new
  end
$ cucumber
Failing on uninitialized constant BooksController::Book (NameError)
Solution: Add model to make Rails happy:

$ vi app/models/book.rb
class Book < ActiveRecord::Base
  attr_accessible :name
end
$ cucumber
Failing Step 1 again... Could not find table 'books' (ActiveRecord::StatementInvalid)
Solution: Create a migration:

$ mkdir db/migrate
$ vi db/migrate/20101120141414_create_books.rb

class CreateBooks < ActiveRecord::Migration
  def self.up
    create_table :books do |t|
      t.string :name

      t.timestamps
    end
  end
  def self.down
    drop_table :books
  end
end

Now:

 $ rake db:migrate
 $ rake db:test:prepare

Running cucumber again, we pass. Excellent.

Step 3: When I press "Create"

$ cucumber
On to our next step:

When I press "Create"                   # features/step_definitions/book_steps.rb:9
   TODO (Cucumber::Pending)
   ./features/step_definitions/book_steps.rb:10:in `/^I press "([^"]*)"$/'
   features/book.feature:5:in `When I press "Create"'

Solution: add the Capybara `click_button` matcher:

When /^I press "([^"]*)"$/ do |arg1|
  click_button 'Create'
end
$ cucumber
cucumber fails on action 'create': The action 'create' could not be found for BooksController (AbstractController::ActionNotFound)
Solution: Add the create method to the books controller:

  def create
  end
$ cucumber
Failing again on templates: Missing template books/create with {:handlers=>[:erb
Solution: Let's go ahead and redirect this to the root_path:

def create
  redirect_to books_path       
end
$ cucumber
Failing and failing and failing: The action 'index' could not be found for BooksController.
Solution: Open app/controllers/books_controller.rb, add

def index
end
$ cucumber
Bummer: Missing template books/index with {:handlers=>[:erb.
Solution: Add app/views/books/index.html.erb:

<h2>List books</h2>
$ cucumber

Step 3 now passes cucumber. Onward, through the fog.

Step 4: Then I should be on the book list page

$ cucumber
Then I should be on the book list page  # features/step_definitions/book_steps.rb:13
  TODO (Cucumber::Pending)
  ./features/step_definitions/book_steps.rb:14:in `/^I should be on the book list page$/'
  features/book.feature:6:in `Then I should be on the book list page'

Time to fill in for the next step, this time with a matcher:

Then /^I should be on the book list page$/ do
  page.should have_content('List books')
end

And that passes Step 4 (for now).

Step 5: And I should see "War & Peace"

$ cucumber
And I should see "War & Peace"       # features/step_definitions/book_steps.rb:17
  TODO (Cucumber::Pending)
  ./features/step_definitions/book_steps.rb:18:in `/^I should see "([^"]*)"$/'
  features/book.feature:7:in `And I should see "War & Peace"'

Time to fill in for the next step, this time with a matcher:

Then /^I should see "([^"]*)"$/ do |arg1|
  page.should have_content(arg1)
end
$ cucumber
Not seeing books: expected there to be text "War & Peace" in "List books" .
Solution: Render the book list:

$vi app/views/books/index.html.erb

<h2>List books</h2>
  <%= render @books %>
$ cucumber
Wooo... undefined method `model_name' for NilClass:Class (ActionView::Template::Error).
Solution: Grab the list of books:

def index
  @books = Book.all
end
Still failing... Missing partial books/book with {:handlers=>[:erb,.
Solution: Add the partial app/views/books/_book.html.erb

< %= book.name %>
$ cucumber
expected #has_content?("War & Peace") to return true, got false
Solution: We're almost done, add a little bit of code to the book controller's create method:

  def create
    @book = Book.new(params[:book])
    if @book.save
      redirect_to root_path
    end
  end

Here's what the entire controller class should look like now:

class BooksController < ApplicationController

  def index
    @books = Book.all
  end

  def new
    @book = Book.new
  end

  def create
    @book = Book.new(params[:book])
    if @book.save
      redirect_to root_path
    end
  end
end

And that should be it.

Note:

  • RSpec only for matchers. In the future (2013?), Capybara matchers may be sufficient.
  • All custom step definitions, no web_steps.rb matchers.

Conclusion

This isn't the only way to do this. Here are more references on the same topic:

If you have an article you believe should be linked, let me know in the comments and I'll add it in.

Overall, this was a lot of work. But there's more which could be done. For example, deleting the web_steps.rb file, which is matching all the lines in the scenario, would force us to write our own matchers, and handle the testing code ourselves.

The entire project could be rewritten in RSpec alone, save the feature file.

What would you do? Did you give this 5-step procedure whirl? Leave a note in the comments!

Enjoy!


This entry was posted in rails and tagged , , , , , , . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

23 Comments

  1. Ed Jones
    Posted June 21, 2011 at 3:40 am | Permalink

    Am I missing some setup thing? I tried
    Given I go to the new assessments page # features/step_definitions/web_steps.rb:48
    Can’t find mapping from “the new assessments page” to a path.
    Now, go and add a mapping in C:/Users/Ed/webapps/whendidji3/features/support/paths.rb

    Which is what the Railscasts experienced, followed by adding complex code to the paths file for every single action you wished to spec.
    ???
    (I’m using Rails 3.07, does that mater?)

    • Ed Jones
      Posted June 21, 2011 at 4:46 am | Permalink

      Looks like my cucumber-rails gem needed updating. With 1.0, that step works fine.

      Next question: where is the list of magic English phrases? i.e.

      “Given I go to the new assessment page” works,
      but instead of
      # When I go to the list of assessments
      we’re stuck with
      When I go to path “/assessments”
      nor, as you point out, does the seemingly common
      “the book list page”
      have meaning. So, what does?

      • Posted June 21, 2011 at 4:53 am | Permalink

        Ed, you’re doing great! This is exactly the purpose: drill through your error messages one at a time. For the feature file in the article, your messages should look like mine in the article. For a different feature file, you may get different error messages. Enjoy!

  2. Sanjay
    Posted July 2, 2011 at 11:54 am | Permalink

    Thank you, you saved me some time when I was struggling to get cucumber running with rails 3.1.

    • Posted July 2, 2011 at 6:58 pm | Permalink

      You’re quite welcome.

      If you run this exercise a few more times, you’ll lock it in. I’m working on something similar for Devise, watch for it in a few days.

  3. sharath
    Posted December 2, 2011 at 9:40 pm | Permalink

    Thank you, Dave. As a newbie to ROR it helped a ton.
    You have an excellent talent to write a ror tutorial. I liked the format of error message & solution approach.

  4. Dharin Rajgor
    Posted December 10, 2011 at 5:00 am | Permalink

    Hi Dave

    I am getting error while running features “MiniTest v1.6.0 is out of date.”. I have tried after setting new version also but same error.

    Thanks,
    Dharin Rajgor

    • Posted February 19, 2012 at 2:15 pm | Permalink

      Remove the turn gem from the Gemfile. I’ll fix that in the article.

  5. gil
    Posted March 8, 2012 at 9:58 am | Permalink

    Hi Dave
    I got a fail in step 5 do you understand the reason (i am new in ror/cucumber):
    This is what i get :
    Scenario: User adds a new book # features\book.feature:2
    Given I go to the new book page # features/step_definitions/book_steps.rb:1
    And I fill in “Name” with “War & Peace” # features/step_definitions/book_steps.rb:5
    When I press “Create” # features/step_definitions/book_steps.rb:9
    Then I should be on the book list page # features/step_definitions/book_steps.rb:13
    And I should see “War & Peace” # features/step_definitions/book_steps.rb:17
    expected there to be content “War & Peace” in “Outsidein\n\nList books\n \n\n” (RSpec::Expectations::ExpectationN
    ./features/step_definitions/book_steps.rb:18:in `/^I should see “([^"]*)”$/’
    features\book.feature:7:in `And I should see “War & Peace”‘

    Failing Scenarios:
    cucumber features\book.feature:2 # Scenario: User adds a new book

    1 scenario (1 failed)
    5 steps (1 failed, 4 passed)
    0m8.109s

    • Posted March 8, 2012 at 10:04 am | Permalink

      If you have all of Step 5 implemented, my hunch is you have a typo lurking somewhere.

      Make sure you have the partial implemented.

  6. gil
    Posted March 12, 2012 at 3:52 am | Permalink

    Hi Dave
    You right … the mistake found
    There was space on :
    And i changed it to :
    Appear also on your post … :-)
    p.s
    Is the cucumber plugin can simulate browser tests with integration on facebooker2

    (mmangino)
    If yes, do you know any info/example/links about that… all the examples i found were

    integration with facebooker2(dekart) only…
    Thanks again ,Gil :-)

    • gil
      Posted March 12, 2012 at 5:48 am | Permalink

      No need solved,thanks :-)

  7. gil
    Posted March 12, 2012 at 4:04 am | Permalink

    The script with the space located on step 5 on file app/views/books/_book.html.erb
    between parentheses to percent sign on your post ….
    best,Gil :-)

  8. HumanBeing
    Posted April 6, 2012 at 12:50 pm | Permalink

    I’m having an ActiveModel::MassAssignmentSecurity::Error when running the fifth step.

    I’ve disabled the config.active_record.mass_assignment_sanitizer in config/enviroments/development and I bypassed that error. However, it won’t save the title of the book to the database (it creates a new record with empty title).

    Any ideas? I’ve copy pasted the code in all files.

  9. HumanBeing
    Posted April 6, 2012 at 1:25 pm | Permalink

    Answering myself, seems like attr_accessible :name should be added in the models.py file for this to work in the last Rails version.

  10. Dave
    Posted April 6, 2012 at 4:06 pm | Permalink

    Thanks, if that’s not in the code, it’s never been necessary yet, so this is excellent. I presented this 3 times in the last month or so, and I’ll keep an eye for why or why not attr_accessible is working, or not.

  11. Scott
    Posted May 11, 2012 at 3:55 pm | Permalink

    I’m a newb, and this tutorial was spot on for me. Many thanks!

  12. Scott
    Posted May 11, 2012 at 3:56 pm | Permalink

    btw – I had the same issue as HumanBeing.

    • Posted May 11, 2012 at 3:58 pm | Permalink

      I’ll check for the specifically next time I update the post. (Working in python and c++ at the moment.)

  13. Posted June 6, 2012 at 2:03 am | Permalink

    Thanks for the great post. BTW, I got an error when I run cucumber for the first time:

    $ cucumber
    Using the default profile...
    Please install the postgresql adapter: `gem install activerecord-postgresql-adapter` (pg is not part of the bundle. Add it to Gemfile.) (LoadError)
    ... (stacktrace goes on)
    

    When I put pg gem into Gemfile, cucumber worked properly. But I can’t find pg gem on this post’s Gemfile. And I found no postgresql configs in config/database.yml (all sqlite3.)
    What was the reason for that error, and is putting pg is right solution for the error?
    I use Ruby 1.9.3p194, Rails 3.2.3 and gem 1.8.24.

  14. sasan
    Posted March 1, 2013 at 9:28 am | Permalink

    hi all

    i have one question…how pass arguments from rake file to step definiton cucumber ?

    thanks

One Trackback

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>


dool.in