Introduction
This tutorial guides you through generating a new Rails 4 application with RSpec and Cucumber as testing tools.
If you’d like to learn how to set up a BDD stack on a Rails 5 application, this tutorial is the best next read for you.
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 orlibsqlite3-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. Would you like to learn how to build sustainable Rails apps and ship more often? We’ve recently published an ebook covering just that — “Rails Testing Handbook”. Learn more and download a free copy.