Key Takeaways
- Ensure to integrate RSpec, Capybara, Shoulda-Matchers, and Database Cleaner into your Rails application to streamline and enhance your testing practices.
- Utilize Factory Girl and Faker for efficiently creating and managing test data, allowing for more dynamic and robust test cases.
- Write comprehensive model, controller, and feature specs to fully cover the functionality of your application, ensuring each component behaves as expected.
- Incorporate SimpleCov to monitor test coverage and ensure that all critical paths within your application are tested, helping maintain high-quality code throughout development.
This article was peer reviewed by Thom Parkin. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!
Writing code without testing can be a deceptively smooth ride; one that, unfortunately, comes to a screeching halt when you start working with other developers. This tutorial is aimed at helping Ruby on Rails developers (especially the new folks) in setting up an RSpec test suite using best practices, in the Behaviour Driven Development way. I know that testing can be pain when you are just getting into it. There are lots of resources online to help you on this journey. I am hopeful this will be of benefit to new Ruby on Rails developers since I just started getting into testing myself.
In this tutorial we will learn the following: * How to setup – RSpec, Capybara, Shoulda-Matchers, Database Cleaner * How to create a factory using Factory Girl Rails and Faker * How to write Model Specs * How to write Controller Specs * How to write Feature Specs * How to check spec coverage using SimpleCov
Rails Application
rails new myapp
Installing RSpec
Open up your Gemfile and add the rspec-rails
gem to the development
and test
group:
group :development, :test do
gem 'byebug'
gem 'rspec-rails', '~> 3.4'
end
Install the gems:
bundle install
Now run:
rails generate rspec:install
This adds the spec directory and some skeleton files, including a .rspec file. Before going further, go ahead and remove the test directory that was generated with our Rails application.
Shoulda-Matchers and Database Cleaner
Open up your Gemfile and add a test
group containing the shoulda-matcher
and database_cleaner
gems:
Gemfile
group :test do
gem 'shoulda-matchers', '~> 3.0', require: false
gem 'database_cleaner', '~> 1.5'
end
Run bundle install
.
Shoulda-Matchers Configuration
Shoulda-Matchers provides one-line matchers to RSpec used in testing Rails functionality which we will see briefly. Open your spec/rails_helper.rb file and configure shoulda-matchers
to work with RSpec by pasting in the following:
require 'shoulda/matchers'
Shoulda::Matchers.configure do |config|
config.integrate do |with|
with.test_framework :rspec
with.library :rails
end
end
Database Cleaner Configuration
Database Cleaner is a set of strategies for cleaning your database in Ruby between test runs. It helps to ensure a clean state for testing.
To integrate database_cleaner
, make the following adjustment to spec/rails_helper.rb:
config.use_transactional_fixtures = false
Create a new directory called support inside of your spec directory:
mkdir spec/support
Inside it, create a new file, database_cleaner.rb and paste in the following:
RSpec.configure do |config|
config.before(:suite) do
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.strategy = :transaction
end
config.before(:each, :js => true) do
DatabaseCleaner.strategy = :truncation
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
end
With that, database_cleaner
is set up and will clean the database between each unit test and test suite.
Capybara Setup
Capybara is an automation framework used for creating functional tests that simulates how users will interact with your application. Add the capybara
gem to the :development, :test
group in your Gemfile, the same group where you added rspec-rails
:
gem 'capybara', '~> 2.5'
and bundle install
.
Open up your spec_helper.rb and require the capybara gem:
***spec/spec_helper.rb***
...
require 'capybara/rspec'
Faker and Factory Girl Setup
Faker is useful in generating random data for your test. You will see how to use it with factory_girl_rails
in making factory. Add the faker
gem to the :test
group in your Gemfile:
gem 'faker', '~> 1.6.1'
Factory Girl allows you create objects that you need in your tests which can include default values. With faker, you will be able to create random objects for your test instead of using just one default value.
Add factory_girl_rails
gem to :development, :test
group:
gem 'factory_girl_rails', '~> 4.5.0'
Then bundle install
.
At this point, your Gemfile should look like this:
source 'https://rubygems.org'
gem 'rails', '4.2.4'
gem 'sqlite3'
gem 'sass-rails', '~> 5.0'
gem 'uglifier', '>= 1.3.0'
gem 'coffee-rails', '~> 4.1.0'
gem 'jquery-rails'
gem 'turbolinks'
gem 'jbuilder', '~> 2.0'
gem 'sdoc', '~> 0.4.0', group: :doc
group :development, :test do
gem 'byebug'
gem 'rspec-rails', '~> 3.4'
gem 'factory_girl_rails', '~> 4.5'
gem 'capybara', '~> 2.5'
end
group :development do
gem 'web-console', '~> 2.0'
gem 'spring'
end
group :test do
gem 'shoulda-matchers', '~> 3.0', require: false
gem 'database_cleaner', '~> 1.5'
gem 'faker', '~> 1.6.1'
end
Creating a Factory
Create a directory named factories in your spec folder, also adding a file named after your model. E.g contacts.rb
***spec/factories/contacts.rb***
FactoryGirl.define do
factory :contact do
full_name { Faker::Name.name }
email { Faker::Internet.email }
phone_number { Faker::PhoneNumber.phone_number }
address { Faker::Address.street_address }
end
end
In the above, I just showed you how to create fixtures using factory_girl_rails
and faker
. You do not have to explicitly enter objects. Factory Girl uses the random (fake) values generated by faker
to create factories that will be used whenever you run your test.
Model Specs
We want to ensure that the factory we created above is valid for the model. Create a file in your model/spec folder for your model:
***spec/models/contact_spec.rb***
require 'rails_helper'
RSpec.describe Contact, type: :model do
it "has a valid factory" do
expect(contact).to be_valid
end
end
From your terminal run:
rspec spec/models/contact_spec.rb
This will show an error, because you do not have Contact
model. Go to the terminal, create the model, and migrate your database:
rails g model Contact full_name:string email:string phone_number:integer address:text
rake db:migrate
Run the spec again and viola! It should pass :)
We’ll wrap up the model test using shoulda-matchers
to validate the presence of full name
, email
, phone number
and address
. In your contact model spec, create a new describe
block:
***spec/models/contact_spec.rb***
describe Contact do
it { is_expected.to validate_presence_of(:full_name) }
it { is_expected.to validate_presence_of(:email) }
it { is_expected.to validate_presence_of(:phone_number) }
it { is_expected.to validate_presence_of(:address) }
end
Run your spec and watch it fail. To have it pass, go to the model and implement the validation code:
***app/model/contact.rb***
class Contact < ActiveRecord::Base
validates_presence_of :full_name, :email, :phone_number, :address
end
Run the spec again and it will pass. Before you move to controller spec, I want to explain some of the terms we have used above.
RSpec DSL Pieces
RSpec is a DSL for creating executable examples of how code is expected to behave, organized in groups.
describe
creates an example group. It takes an argument that tells what the spec is about. The argument can be a class, module, method or a string description.
it
creates an example and takes a description of the example. (Example; it has status code 400
). It is a best practice to limit the spec description to 40 characters or less. If it needs to be longer, you probably should consider using a context
to create “sub-contexts” of a describe
block.
expect
lets you express expected outcomes on an object in an example. It takes an object or block and is used with either to
or not_to
alongside a matcher (e.g eq()
, be_valid
). While there are many built-in matchers, gems like Shoulda Matchers add even more to make your specs expressive and concise.
Controller Spec
The first controller spec will test if a new contact gets created with valid attributes. Open up your terminal and generate the controller:
rails g controller Contacts
This will automatically generate the necessary folders for your controller spec. You can see them at spec/controllers.
It’s time to write the spec to test the create
action:
***spec/controllers/contacts_controller_spec.rb***
require 'rails_helper'
RSpec.describe ContactsController, type: :controller do
describe "POST #create" do
context "with valid attributes" do
it "create new contact" do
post :create, contact: attributes_for(:contact)
expect(Contact.count).to eq(1)
end
end
end
end
Run the spec:
rspec spec/controllers/contacts_controller_spec.rb
This will fail because you do not have a route that matches your controller. Open up the routes file and drop this in:
***config/routes.rb***
...
resources :contacts
Run your spec again and it should still fail with the error The action 'create' could not be found for ContactsController
.
Open up your controller and drop in the necessary code to create a new contact.
***app/controllers/contacts_controller.rb***
class ContactsController < ApplicationController
def new
@contact = Contact.new
end
def create
@contact = Contact.new(contact_params)
respond_to do |format|
if @contact.save
format.html { redirect_to @contact }
format.json { render :show, status: :created, location: @contact }
else
format.html { render :new }
format.json { render json: @contact.errors, status: :unprocessable_entity}
end
end
end
private
def contact_params
params.require(:contact).permit(:full_name, :email, :phone_number, :address)
end
end
Save and run the spec one more time, and it should pass.
Following the spec above, write a spec that uses invalid attributes to create a new contact. This spec should check that the contact is not created:
***spec/controllers/contacts_controller_spec.rb***
context "with invalid attributes" do
it "does not create a new contact" do
post :create, contact: attributes_for(:invalid_contact)
expect(Contact.count).to eq(0)
end
end
end
Using the spec you wrote above as an example, you can churn out specs for other controller actions.
Feature Spec
Feature Specs are high-level tests that work through your application ensuring that every component works. They are usually written from the perspective of a user.
For the purpose of this tutorial, you will write a spec to test the creation of a new contact. Using the capybara gem, the specs will fill in a form with valid attributes and test that the show page displays the expected text when a contact is created.
Open your terminal or text editor and create folders and a file following the path below:
spec/features/contacts/create_spec.rb
Paste the following code into that file:
***spec/features/contacts/create_spec.rb***
require 'rails_helper'
RSpec.feature "Contact", :type => :feature do
scenario "Create a new contact" do
visit "/contacts/new"
fill_in "Full name", :with => "My Name"
fill_in "Email", :with => "my@email.com"
fill_in "Phone number", :with => "123456789"
fill_in "Address", :with => "34, Allen Way, OA"
click_button "Create Contact"
expect(page).to have_text("My Name")
end
end
The spec above tests if the show page has the text My Name
which is the value written into the full name
input. Run this spec and it should fail.
First add a show
action to your controller:
***app/controllers/contacts_controller.rb
def show
@contact = Contact.find(params[:id])
end
Create the following files in your contacts view: new.html.erb, _form.html.erb, show.html.erb and fill in with the following code:
***app/views/contacts/_form.html.erb***
<%= form_for(@contact) do |f| %>
<% if @contact.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@contact.errors.count, "error") %> prohibited this contact from being saved:</h2>
<ul>
<% @contact.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :full_name %><br>
<%= f.text_field :full_name %>
</div>
<div class="field">
<%= f.label :email %><br>
<%= f.text_area :email %>
</div>
<div class="field">
<%= f.label :phone_number %><br>
<%= f.text_area :phone_number %>
</div>
<div class="field">
<%= f.label :address %><br>
<%= f.text_area :address %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
***app/views/contacts/new.html.erb***
<h2>Create new contact</h2>
<%= render 'form' %>
***app/views/contacts/show.html.erb***
<p id="notice"><%= notice %></p>
<p>
<strong>Full Name:</strong>
<%= @contact.full_name %>
</p>
<p>
<strong>Email:</strong>
<%= @contact.email %>
</p>
<p>
<strong>Phone Number:</strong>
<%= @contact.phone_number %>
</p>
<p>
<strong>Address:</strong>
<%= @contact.address %>
</p>
Run the spec and it should pass this time.
Coverage using SimpleCov
The SimpleCov gem is a code coverage analysis tool for Ruby. You will use it in your application to see how much of your code is tested. To install, open up the Gemfile and add to the test
group:
gem 'simplecov', :require => false
Run bundle install
.
Next open your spec helper are require simplecov
require 'simplecov'
SimpleCov.start
The next time you run the specs, a new folder with the name coverage will be generated. Open up your browser and point to: your-app-directory/coverage/index.html to see the coverage statistics for your application. You’ll want to add the coverage folder to .gitignore file so it does not get added to your remote repository.
Conclusion
This article aims to give you a foundation for setting up RSpec for your Rails application. These are the basic steps and, from here, you’ll be able to explore the vast syntax of RSpec and start refining your specs. At this point, you should be able to:
- Set up a test suite for your application.
- Write model, controller, and feature specs.
Here are some more resources to help you:
With more practice, in no time you will be fluent in testing your Rails application.
Frequently Asked Questions about Rails and RSpec Best Practices
What are the key benefits of using RSpec in Rails?
RSpec is a testing tool for Ruby, commonly used with Rails. It is designed to make Test-Driven Development (TDD) a productive and enjoyable experience. The key benefits of using RSpec in Rails include its readability, flexibility, and modularity. RSpec uses a simple, English-like syntax that makes it easy to read and write tests. It is also highly flexible, allowing you to write tests in a way that suits your application and testing style. Furthermore, RSpec is modular, meaning you can choose to use its various components independently or together, depending on your needs.
How do I install RSpec in my Rails application?
To install RSpec in your Rails application, you need to add it to your Gemfile. Open your Gemfile and add the following line: gem 'rspec-rails', '~> 3.8'
. Then, run bundle install
to install the gem. After that, you need to set up RSpec in your application by running rails generate rspec:install
. This will create a spec directory and some files that are used for configuration.
What are some best practices for writing RSpec tests in Rails?
Some best practices for writing RSpec tests in Rails include keeping your tests DRY (Don’t Repeat Yourself), using factories instead of fixtures, and testing behavior, not implementation. You should also strive to write clear, readable tests and to keep your test suite organized.
How can I run my RSpec tests?
To run your RSpec tests, you can use the rspec
command followed by the path to the spec file. For example, rspec spec/models/user_spec.rb
would run the tests in the user_spec.rb file. If you want to run all your tests, you can simply use the rspec
command without specifying a file.
How can I use RSpec with Capybara for feature testing?
Capybara is a tool that integrates with RSpec to allow for feature testing, which simulates how a user would interact with your application. To use RSpec with Capybara, you need to add Capybara to your Gemfile and then require it in your spec_helper.rb or rails_helper.rb file. You can then use Capybara’s DSL in your feature specs.
What is the difference between let
and before
in RSpec?
In RSpec, let
and before
are both used to set up state for your tests, but they work in slightly different ways. let
is lazy-evaluated, meaning it is not evaluated until it is called. This can improve performance for expensive setup. before
, on the other hand, is evaluated before each example in the group.
How can I test private methods with RSpec?
In general, you should not test private methods directly. Instead, you should test the public methods that use them. However, if you really need to test a private method, you can do so by sending the method to the object.
How can I use mocks and stubs in RSpec?
Mocks and stubs are used in RSpec to isolate the object under test from its dependencies. A stub replaces a method with code that returns a specified result, while a mock also asserts that the method is called a certain number of times.
What is the purpose of the describe
and it
blocks in RSpec?
The describe
and it
blocks in RSpec are used to organize your tests and provide context. The describe
block is used to group related examples, while the it
block is used to specify a single example.
How can I share common setup code across multiple tests in RSpec?
In RSpec, you can share common setup code across multiple tests using before
, after
, and around
hooks. These hooks allow you to specify code that should be run before, after, or around each example or group of examples.
Kingsley Silas is a web developer from Nigeria. He has a hunger for acquiring new knowledge in every aspect he finds interesting.