Ruby on Rails 3 Testing with Cucumber

Install Cucumber for Ruby on Rails 3

Install Cucumber, Rspec-rails and capybara gem

sudo gem install cucumber-rails
sudo gem install database_cleaner
sudo gem install rspec-rails
sudo gem install capybara
  • Cucumber is a behavior driven development (BDD) framework in particular good for integration and functional tests
  • RSpec is a behavior driven development (BDD) framework for low level testing in the Ruby language
  • database_cleaner performs DB cleanup in testing
  • capybara simulating a browser, automating a browser or setting expectations using the matchers

Using Cucumber to Test a Rails 3 Application

Create a new Rails application store

rails new store -d mysql

Edit the Gemfile

cd store
vi Gemfile

Include the Cucumber gems in a Rails 3 application

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

Install the Gems

bundle install

Install the cucumber skeleton files to a Rails 3 application (bootstrap a Rails 3 application)

% 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

Edit the DB connection information and put the valid DB configuration information

vi config/database.yml

Create the MySQL DB

rake db:create
rake db:migrate

Smoke test: run the Cucumber features

rake cucumber

Create Cucumber Features

Create a Cucumber Feature for Rails 3 application testing

vi features/manage_store.feature

Feature

Cucumber Feature is the business user testing specification written in plain English with a specific format

Feature Format

Feature: ...
  In order ...
  Some Actor ...
  Should ...

  Scenarior: ...
    Given ...
    And ...

    When ...
    AND ...

    Then ...
    AND ...

Feature Sample

Feature: Manage Orders
  In order to increase revenue
  A shopper
  Should create and manage orders

  Scenario: View previous orders
    Given I have orders Christmas books, Textbooks in my account
    And all have shipped

    When I go to the list of orders

    Then I should see "Christmas books"
    And I should see "Textbooks"

In order should contain one of the following business goals

to protect revenue
to increase revenue
to manage cost
to increase brand value
to make the product remarkable
to provide more value to your customers

Test the Cucumber Features

Run rake cucumber to test the Cucumber Features

% rake cucumber
(in /home/ec2-user/test/store)
bundle exec /usr/bin/ruby -I "/usr/lib64/ruby/gems/1.8/gems/cucumber-0.10.2/lib:lib" "/usr/lib64/ruby/gems/1.8/gems/cucumber-0.10.2/bin/cucumber"  --profile default
Using the default profile...
Feature: Manage Orders
  In order to increase revenue
  A shopper
  Should create and manage orders

  Scenario: View previous orders                                 # features/manage_store.feature:6
    Given I have orders Christmas books, Textbooks in my account # features/manage_store.feature:7
      Undefined step: "I have orders Christmas books, Textbooks in my account" (Cucumber::Undefined)
      features/manage_store.feature:7:in `Given I have orders Christmas books, Textbooks in my account'
    And all have shipped                                         # features/manage_store.feature:8
      Undefined step: "all have shipped" (Cucumber::Undefined)
      features/manage_store.feature:8:in `And all have shipped'
    When I go to the list of orders                              # features/step_definitions/web_steps.rb:48
    Then I should see "Christmas books"                          # features/step_definitions/web_steps.rb:105
    And I should see "Textbooks"                                 # features/step_definitions/web_steps.rb:105

1 scenario (1 undefined)
5 steps (3 skipped, 2 undefined)
0m0.597s

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

Given /^I have orders Christmas books, Textbooks in my account$/ do
  pending # express the regexp above with the code you wish you had
end

Given /^all have shipped$/ do
  pending # express the regexp above with the code you wish you had
end

Cucumber shows the failures and the testing steps that are yet to implement

Undefined step: "I have orders Christmas books, Textbooks in my account" (Cucumber::Undefined)
...

Given /^I have orders Christmas books, Textbooks in my account$/ do
   ...
end

(3 skipped, 2 undefined)

Implement the testing steps and Rails model code for Cucumber on Rails 3

Create Cucumber step definitions

vi features/step_definitions/order_steps.rb

For missing Cucumber steps

Given /^I have orders Christmas books, Textbooks in my account$/ do
  pending # express the regexp above with the code you wish you had
end

Given /^all have shipped$/ do
  pending # express the regexp above with the code you wish you had
end

Implement the Cucumber Given

Given /^I have orders (.+) in my account$/ do |orders|
 orders.split(', ').each do |order_name|
    Order.create(:name => order_name)
 end
end

Given /^all have shipped$/ do
...
end
  • Cucumber uses regular expression to match string and pass it to the step definitions
  • "Christmas books, Textbooks" will be assigned to orders

To pass multiple parameters to the step function

Given /^I have orders (.+), (.+) in my account$/ do |order1, order2|
  Order.create(:name => order1)
  Order.create(:name => order2)
end

Run Cucumber

% rake cucumber
Using the default profile...
Feature: Manage Orders
  In order to increase revenue
  A shopper
  Should create and manage orders

  Scenario: View previous orders                                 # features/manage_store.feature:6
    Given I have orders Christmas books, Textbooks in my account # features/step_definitions/order_steps.rb:1
      uninitialized constant Order (NameError)
      ./features/step_definitions/order_steps.rb:3
      ./features/step_definitions/order_steps.rb:2:in `each'
      ./features/step_definitions/order_steps.rb:2:in `/^I have orders (.+) in my account$/'
      features/manage_store.feature:7:in `Given I have orders Christmas books, Textbooks in my account'
    And all have shipped                                         # features/step_definitions/order_steps.rb:7
    When I go to the list of orders                              # features/step_definitions/web_steps.rb:48
    Then I should see "Christmas books"                          # features/step_definitions/web_steps.rb:105
    And I should see "Textbooks"                                 # features/step_definitions/web_steps.rb:105

Failing Scenarios:
cucumber features/manage_store.feature:6 # Scenario: View previous orders

1 scenario (1 failed)
5 steps (1 failed, 4 skipped)
  • Complain Order model is missing
    Given I have orders Christmas books, Textbooks in my account # features/step_definitions/order_steps.rb:1
          uninitialized constant Order (NameError)

    Write development code for the failed step: Implement a Order model with RSpec used in Cucumber Testing

    % rails generate model order name:string
          invoke  active_record
          create    db/migrate/20110510190917_create_orders.rb
          create    app/models/order.rb
          invoke    rspec
          create      spec/models/order_spec.rb

    Migrate Rails DB

    rake db:migrate

Run cucumber

% rake cucumber
Using the default profile...
Feature: Manage Orders
  In order to increase revenue
  A shopper
  Should create and manage orders

  Scenario: View previous orders                                 # features/manage_store.feature:6
    Given I have orders Christmas books, Textbooks in my account # features/step_definitions/order_steps.rb:1
    And all have shipped                                         # features/step_definitions/order_steps.rb:7
    When I go to the list of orders                              # features/step_definitions/web_steps.rb:48
      Can't find mapping from "the list of orders" to a path.
      Now, go and add a mapping in /home/ec2-user/test/store/features/support/paths.rb (RuntimeError)
      ./features/support/paths.rb:27:in `path_to'
      ./features/step_definitions/web_steps.rb:49:in `/^(?:|I )go to (.+)$/'
      features/manage_store.feature:9:in `When I go to the list of orders'
    Then I should see "Christmas books"                          # features/step_definitions/web_steps.rb:105
    And I should see "Textbooks"                                 # features/step_definitions/web_steps.rb:105

Failing Scenarios:
cucumber features/manage_store.feature:6 # Scenario: View previous orders

1 scenario (1 failed)
5 steps (1 failed, 2 skipped, 2 passed)
  • The step definition for "When I go to the list of orders" is already defined by capybara in features/step_definitions/web_steps.rb
    • When I go to a Web page that I called "the list of orders"
  • However, there is no definition for "the list of orders" Web page in Cucumber
    Can't find mapping from "the list of orders" to a path.
  • All the pre-defined step definitions in capybara:
    # Multi-line step scoper
    When /^(.*) within ([^:]+):$/ do |step, parent, table_or_string|
      with_scope(parent) { When "#{step}:", table_or_string }
    end
    
    Given /^(?:|I )am on (.+)$/ do |page_name|
      visit path_to(page_name)
    end
    
    When /^(?:|I )go to (.+)$/ do |page_name|
      visit path_to(page_name)
    end
    
    When /^(?:|I )press "([^"]*)"$/ do |button|
      click_button(button)
    end
    
    When /^(?:|I )follow "([^"]*)"$/ do |link|
      ...
    end
    
    When /^(?:|I )fill in "([^"]*)" with "([^"]*)"$/ do |field, value|
      ...
    end
    
    When /^(?:|I )fill in "([^"]*)" for "([^"]*)"$/ do |value, field|
      ...
    end
    
    When /^(?:|I )fill in the following:$/ do |fields|
      ...
    end
    
    When /^(?:|I )select "([^"]*)" from "([^"]*)"$/ do |value, field|
      ...
    end
    
    When /^(?:|I )check "([^"]*)"$/ do |field|
      ...
    end
    
    When /^(?:|I )uncheck "([^"]*)"$/ do |field|
      ...
    end
    
    When /^(?:|I )choose "([^"]*)"$/ do |field|
      ...
    end
    
    When /^(?:|I )attach the file "([^"]*)" to "([^"]*)"$/ do |path, field|
      ...
    end
    
    Then /^(?:|I )should see "([^"]*)"$/ do |text|
      ...
    end
    
    Then /^(?:|I )should see \/([^\/]*)\/$/ do |regexp|
      ...
    end
    
    Then /^(?:|I )should not see "([^"]*)"$/ do |text|
      ...
    end
    
    Then /^(?:|I )should not see \/([^\/]*)\/$/ do |regexp|
      ...
    end
    
    Then /^the "([^"]*)" field(?: within (.*))? should contain "([^"]*)"$/ do |field, parent, value|
      ...
    end
    
    Then /^the "([^"]*)" field(?: within (.*))? should not contain "([^"]*)"$/ do |field, parent, value|
      ...
    end
    
    Then /^the "([^"]*)" checkbox(?: within (.*))? should be checked$/ do |label, parent|
      ...
    end
    
    Then /^the "([^"]*)" checkbox(?: within (.*))? should not be checked$/ do |label, parent|
      ...
    end
    
    Then /^(?:|I )should be on (.+)$/ do |page_name|
      ...
    end
    
    Then /^(?:|I )should have the following query string:$/ do |expected_pairs|
      ...
    end
    
    Then /^show me the page$/ do
      save_and_open_page
    end

Implement the Cucumber Path, Rails Route, Controller & View

Map the feature Web page name "the list of orders" to the Cucumber's path

vi features/support/paths.rb
when /the home\s?page/
   '/'
when /the list of orders/
   orders_path
  • Map "the list of orders" to the pre-defined Rails constant orders_path

Create the Route for "orders_path"

config/routes.rb
resources :orders

Generate a Rails controller for orders

rails generate controller orders index

Add business logic to the Rails controller

app/controllers/orders_controller.rb
class OrdersController < ApplicationController
  def index
    @orders = Order.all
  end
end

Add the Rails view

app/views/orders/index.html.erb
<% for order in @orders %>
 <%= order.name %>
<% end %>

Run the Cucumber

% rake cucumber
(in /home/ec2-user/test/store)
bundle exec /usr/bin/ruby -I "/usr/lib64/ruby/gems/1.8/gems/cucumber-0.10.2/lib:lib" "/usr/lib64/ruby/gems/1.8/gems/cucumber-0.10.2/bin/cucumber"  --profile default
Using the default profile...
Feature: Manage Orders
  In order to increase revenue
  A shopper
  Should create and manage orders

  Scenario: View previous orders                                 # features/manage_store.feature:6
    Given I have orders Christmas books, Textbooks in my account # features/step_definitions/order_steps.rb:1
    And all have shipped                                         # features/step_definitions/order_steps.rb:7
    When I go to the list of orders                              # features/step_definitions/web_steps.rb:48
    Then I should see "Christmas books"                          # features/step_definitions/web_steps.rb:105
    And I should see "Textbooks"                                 # features/step_definitions/web_steps.rb:105

1 scenario (1 passed)
5 steps (5 passed)
  • Cucumber test passed

More Advance Cucumber Features Sample

Feature

features/manage_store.feature
Feature: Manage Orders
  To increase revenue
  A shopper
  Should create an order

  Scenario: Create new marketing campaign for new shopper
    Given I have no orders
    And I am on the list of orders
    When I follow "New order"
    And I fill in "order_shipping" with "express"
    And I press "Create"
    Then I should see "express"
    And I should have 1 order
  • When I am in "the list of orders" page
  • And I click on the link "New Order"
  • Then I put "express" in the shipping field
  • And press the button with name "Create"

Implement Cucumber steps: Given and Then

features/step_definitions/order_steps.rb
Given /^I have no orders/ do
  Order.delete_all
end

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

For

When I follow "New order"

Implement the "New order" link on the view

app/views/orders/index.html.erb
<% for order in @orders %>
 <%= order.name %>
<% end %>

<p> <%= link_to "New order", new_order_path %> </p>

Add logic to the Controller

app/controllers/orders_controller.rb
class OrdersController < ApplicationController
  def index
    @orders = Order.all
  end

  def new
    @order = Order.new
  end

  def create
    @order = Order.create(params[:order])
    redirect_to orders_path
  end
end

Create a view of creating an order, for

And I fill in "order_shipping" with "express"
And I press "Create"

Implement

app/views/orders/new.html.erb
<% form_for(@order) do |f| %>
  <p>
    <%= f.label :shipping %><br />
    <%= f.text_field :shipping %>
  </p>
  <p>
    <%= f.submit 'Create' %>
  </p>
<% end %>

Add a new "shipping" column to the DB

rails generate migration AddShippingToOrder shipping:string
rake db:migrate

Display the shipping information on the index page

app/views/orders/index.html.erb
<% for order in @orders %>
 <%= order.name %>
 <%= order.shipping %>
<% end %>

<p> <%= link_to "New order", new_order_path %> </p>

Nest Cucumber Nested Step Definition

Scenario: Accessing member's only article
  Given I signed in as "dave"
  When I follow "Submit"
  Then I should see "Member only content"
Given /^I signed in as as "([^"]*)"$/ do |user_name|
  user = User.find_by_name(user_name)
  Given "#{user_name} is a member"
  And %[I go to the login page]
  And %[I fill in "Username" with "#{user.login}"]
  And %[I fill in "Password" with "#{user.password}"]
  And %[I press "Login"]
end

Re-use Then and And step definition

When /Some action (.*)/ do |match|
  steps %Q{
    Then some matcher "#{match}"
    And another matcher "#{match}"
  }
end