Note: An updated version of the tutorial is available at Semaphore
Community.
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.
gherkin
# 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
erb
<%% @posts.each do |post| %>
<%%= post.title %>
<%%= post.content %>
<%% end %>
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.