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.
Want to discuss this article? Join our Discord.