Setting up the BDD stack on a new Rails 4 application

Brought to you by

Semaphore

Learn More

Semaphore has been, and continues to be, developed using the behavior-driven development process. In this post, I will take you through the steps needed to set up our prefered BDD stack on a new Rails 4 application and explain why each tool is important.

Create the application

$ rails new myapp

Install RSpec

We prefer RSpec to other testing frameworks since we find it more natural and expressive. For example, I never find myself using the word “assert” in everyday talk, while I use the word “should” regularly. Also, RSpec’s language for writing tests and output is very readable and can serve as documentation.

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

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

Install the gem:

$ bundle install

Bootstrap the app with RSpec:

$ rails generate rspec:install

Create the RSpec binstub. In short, the binstub will allow you to run RSpec with bin/rspec instead of bundle exec rspec:

$ bundle binstubs rspec-core

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 install

Let’s take a look at a simple validation spec.

If you are validating the presence of post’s title:

class Post < ActiveRecord::Base
  validates :title, presence: true
end

without shoulda-matchers the spec might look something like the following:

require 'spec_helper'

describe Post do

  describe "title validation" do

    context "title is present" do

      before(:each) do
        @post = Post.new(title: "My first post")
      end

      it "does not add an error on the 'title' attribute" do
        @post.should have(0).error_on(:title)
      end

    end

    context "title is not present" do

      before(:each) do
        @post = Post.new
      end

      it "adds an error on the 'title' attribute" do
        @post.should have(1).error_on(:title)
      end

    end

  end

end

and with shoulda-matchers:

require 'spec_helper'

describe Post do
  it { should validate_presence_of(:title) }
end

Install Factory Girl

Factory Girl is “a library for setting up Ruby objects as test data” or more precisely it is a fixtures replacement.

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

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

Install the gem:

$ bundle install

Basically, Factory Girl will allow you to create objects that you need in your tests without providing a value for each required attribute. If you don’t provide a value for a required attribute Factory Girl will use a default value that you defined in factory’s definition.

Factory Girl also has a more pleasant system for defining record associations than when using fixtures.

Let’s define a post factory:

FactoryGirl.define do
  factory :post do
    title "My first post"
    content "Hello, behavior-driven development world!"
  end
end

Now, if both the title and content attributes are required to create a valid post, instead of writing in our spec something like the following:

require 'spec_helper'

describe Post do

  describe "creation" do

    context "valid attributes" do

      it "should be valid" do
        post = Post.new(title: "My first post", content: "Hello, behavior-driven development world!")

        post.should be_valid
      end

    end

    context "invalid attributes" do

      it "should not be valid" do
        post = Post.new(title: "My first post", content: "")

        post.should_not be_valid
      end

    end

  end

end

you can just write:

require 'spec_helper'

describe Post do

  describe "creation" do

    context "valid attributes" do

      it "should be valid" do
        post = FactoryGirl.build(:post)

        post.should be_valid
      end

    end

    context "invalid attributes" do

      it "should not be valid" do
        post = FactoryGirl.build(:post, title: "")

        post.should_not be_valid
      end

    end

  end

end

Make sure everything is connected and working

Create a Post model:

$ rails generate model Post title:string content:text

      invoke  active_record
      create    db/migrate/20130726125040_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 now also creates a model spec and a ‘posts’ factory. That’s the reason why we included the rspec-rails and factory_girl_rails gems in the development group of the Gemfile.

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

require 'spec_helper'

describe Post do

  it { should validate_presence_of(:title) }
  it { should ensure_length_of(:title).is_at_least(5) }
  it { should validate_presence_of(:content) }
  it { should ensure_length_of(:content).is_at_least(10) }

end

And update the Post model with validation definitions:

class Post < ActiveRecord::Base

  validates :title, presence: true, length: { minimum: 5  }
  validates :content, presence: true, length: { minimum: 10  }

end

Before running the spec make sure to apply the migration and prepare the test database by recreating it from db/schema.rb.

$ bundle exec rake db:migrate db:test:prepare

After running the spec you can see it pass:

$ bin/rspec spec/models/post_spec.rb

Install Cucumber

Cucumber helps us both focus on the feature-level and as a high-level integration testing tool.

Add cucumber-rails gem to the test group of the Gemfile.

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

You can also add the database_cleaner gem which is not required, but it will save you a lot of heartache. It’s used to ensure a clean database state for testing.

Install the gems:

$ bundle install

Bootstrap the app with Cucumber:

$ rails generate cucumber:install

Create the Cucumber binstub:

$ bundle binstubs cucumber

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 'factory_girl_rails'
  gem 'selenium-webdriver'
end

And install it:

$ bundle install

Make sure Cucumber is working correctly

To do that, let’s develop a simple feature.

# 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
# 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 root_path
end

Then(/^I should see the "(.*?)" post$/) do |title|
  @post = Post.find_by_title(title)

  page.should have_content(@post.title)
  page.should have_content(@post.content)
end
# config/routes.rb
Myapp::Application.routes.draw do

  root to: "posts#index"

end
# app/controllers/posts_controller.rb
class PostsController < ApplicationController

  def index
    @posts = Post.all
  end

end
<!-- 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:

$ bin/cucumber features/home_page.feature

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

Related Articles

Subscribe to receive email updates on continuous integration from Semaphore.

comments powered by Disqus