Setting Up the BDD Stack on a New Rails 4 Application

Guide for generating a new Rails 4 application with RSpec and Cucumber as testing tools.

Brought to you by

Semaphore

Introduction

This tutorial guides you through generating a new Rails 4 application with RSpec and Cucumber as testing tools.

RSpec is a testing tool often used for writing unit tests, while Cucumber is a tool for writing acceptance tests. Both tools are popular choice for behavior-driven development (BDD).

In the tutorial, we assume you are working on a Unix-like operating system.

Prerequisites

  • Ruby.
  • SQLite (sqlite-devel package in Red Hat based distributions or libsqlite3-dev in Debian based distributions).

Create the application

To generate a new Rails application, we will need rails gem. Install the latest version of rails with:

gem install rails

Generate a new Rails application called myapp:

rails new --skip-test-unit --skip-bundle myapp

The --skip-test-unit option skips configuring test.unit. In the tutorial, we use RSpec instead.

The --skip-bundle option prevent running bundle install automatically. We will do that manually:

cd myapp
bundle install --path vendor/bundle

The --path parameter tells the bundle install command to install gems in the myapp/vendor/bundle directory. If you leave off the parameter, gems will be installed globally. It's a good practice to keep gems installed locally - especially if you are working on more than one Ruby application on the development machine.

Installing debugger gem can fail if you have Ruby 2 on your development machine:

An error occurred while installing debugger (1.6.8), and Bundler cannot
continue.
Make sure that `gem install debugger -v '1.6.8'` succeeds before bundling.

If that happens, open Gemfile in the application directory and replace gem 'debugger' with gem 'byebug'. Run bundle install --path vendor/bundle again an this time gems should install successfully. Byebug is a debugger that supports Ruby 2.

Install RSpec

Add rspec-rails gem to the development and test groups of your Gemfile.

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

Install the gem:

bundle

Bootstrap the application with RSpec:

bundle exec rails generate rspec:install

The task will generate RSpec configuration files.

Install shoulda-matchers

shoulda-matchers lets us spec common Rails functionality, like validations and associations, with less code.

Add shoulda-matchers gem to the test group of your Gemfile:

group :test do
  gem 'shoulda-matchers'
end

Install the gem:

bundle

Before you can use shoulda-matchers, you need to configure it by choosing the test framework and features of shoulda-matchers you want to use. Open spec/rails_helper.rb and add the following block to the end:

Shoulda::Matchers.configure do |config|
  config.integrate do |with|
    with.test_framework :rspec
    with.library :rails
  end
end

You can find more info about configuring shoulda-matchers in the offical readme.

Install factory_girl

factory_girl is a library for setting up Ruby objects as test data. It's essentially a fixtures replacement.

Add factory_girl_rails gem to the development and test groups of your Gemfile:

group :development, :test do
  gem 'rspec-rails'
  gem 'factory_girl_rails'
end

Install the gem:

bundle

factory_girl allows you to create objects that are needed in tests without providing a value for each required attribute. If you don't provide a value for a required attribute factory_girl will use the default value that you defined in factory's definition.

Make Sure Everything is Connected and Working

Let's create an example Post model and write specs that will verify that RSpec is working correctly:

$ bundle exec rails generate model Post title:string content:text
invoke active_record
create db/migrate/20140926125040_create_posts.rb
create app/models/post.rb
invoke rspec
create spec/models/post_spec.rb
invoke factory_girl
create spec/factories/posts.rb

Notice, the generator also creates a model spec and a Post factory. That's the reason why we included the rspec-rails and factory_girl_rails gems in the development group of the Gemfile.

Run the migration that will add the new posts table to the database:

bundle exec rake db:migrate

Define a post factory:

# spec/factories/posts.rb
FactoryGirl.define do
  factory :post do
    title "My first post"
    content "Hello, behavior-driven development world!"
  end
end

Update the spec to validate post's title and content:

# spec/models/post_spec.rb
require 'rails_helper'

RSpec.describe Post, type: :model do
  it { is_expected.to validate_presence_of(:title) }
  it { is_expected.to validate_length_of(:title).is_at_least(5) }
  it { is_expected.to validate_presence_of(:content) }
  it { is_expected.to validate_length_of(:content).is_at_least(10) }
end

And update the Post model with validation definitions:

# app/models/post.rb
class Post < ActiveRecord::Base
  validates :title, presence: true, length: { minimum: 5 }
  validates :content, presence: true, length: { minimum: 10 }
end

Run Post specs:

$ bundle exec rspec spec/models/post_spec.rb
....

Finished in 0.01982 seconds (files took 1.26 seconds to load)
4 examples, 0 failures

You can see that specs pass which means that RSpec, shoulda-matchers and factory_girl are properly configured.

Install Cucumber

Add cucumber-rails and database_cleaner gems to the test group of the Gemfile:

group :test do
  gem 'shoulda-matchers'
  gem 'cucumber-rails', require: false
  gem 'database_cleaner'
end

The database_cleaner gem is not required, but it will make your development easier. It's used to ensure a clean database state for testing.

Install the gems:

bundle

Bootstrap the application with Cucumber:

bundle exec rails generate cucumber:install

The task will generate Cucumber configuration files and set up database for Cucumber tests.

Install selenium-webdriver

To be able to run Cucumber scenarios which use JavaScript you need selenium-webdriver. Add it to the test group of your Gemfile:

group :test do
  gem 'cucumber-rails', require: false
  gem 'database_cleaner'
  gem 'selenium-webdriver'
end

And install it:

bundle

Make sure Cucumber is working correctly

To do that, let's develop a simple feature. In the scenario, a user will visit the homepage and see that a post is displayed:

# features/home_page.feature
Feature: Home page

  Scenario: Viewing application's home page
    Given there's a post titled "My first" with "Hello, BDD world!" content
    When I am on the homepage
    Then I should see the "My first" post

Run the scenario and you will see snippets for implementing steps:

$ bundle exec cucumber features/home_page.feature
...

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

Given(/^there's a post titled "(.*?)" with "(.*?)" content$/) do |arg1, arg2|
  pending # express the regexp above with the code you wish you had
end

When(/^I am on the homepage$/) do
  pending # express the regexp above with the code you wish you had
end

Then(/^I should see the "(.*?)" post$/) do |arg1|
  pending # express the regexp above with the code you wish you had
end

Let's copy those steps into features/step_definitions/home_page_steps.rb and edit them:

# features/step_definitions/home_page_steps.rb
Given(/^there's a post titled "(.*?)" with "(.*?)" content$/) do |title, content|
  @post = FactoryGirl.create(:post, title: title, content: content)
end

When(/^I am on the homepage$/) do
  visit "/"
end

Then(/^I should see the "(.*?)" post$/) do |title|
  @post = Post.find_by_title(title)
  expect(page).to have_content(@post.title)
  expect(page).to have_content(@post.content)
end

In these steps we create a post using factory_girl, visit the homepage and check if the post is displayed.

If you run the scenario again, you will see that it fails since the route is not defined. Let's add the new route:

# config/routes.rb
Myapp::Application.routes.draw do
  root to: "posts#index"
end

Now implement the controller that will serve the /posts route:

# app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def index
    @posts = Post.all
  end
end

And create the view that will render Posts#index action and list all posts:

<!-- app/views/posts/index.html.erb -->
<ul>
  <% @posts.each do |post| %>
    <li>
      <%= post.title %><br />
      <%= post.content %>
    </li>
  <% end %>
</ul>

Now run the feature file and you should see it pass:

$ bundle exec cucumber features/home_page.feature
...

1 scenario (1 passed)
3 steps (3 passed)

Congratulations for making it this far. You should now be fully equipped to work in the BDD cycle and deliver clean, working code.

P.S. Semaphore is working on a book "The Ultimate Guide to BDD with Rails". Sign up to receive a FREE copy.

81dc6254e4ae1e5578bfba393a844bd4
Nebojša Stričević

I'm an independent Ruby on Rails and JavaScript consultant. I'm also running TribeLib - a free tool that helps you build, grow and maintain a great company library. Visit my website for more.

on this tutorial so far.
User deleted author {{comment.createdAt}}

Edited on {{comment.updatedAt}}

Cancel

Sign In You must be logged in to comment.