Testing Rails Simple Guide — Part 2

Tim Cheung
Design & Code
Published in
9 min readFeb 14, 2016

--

From part 1, we already know we can use Integration Test and Unit Test to test our rails application. So in this part, we will dive deep to explore how to writing test with RSpec.

Example App for Demonstration

To demonstration how to write the RSpec test, We will use the app called crmdemo.

This is the sample CRM app with one model:

  • Lead — This model will contain company, email & active attribute.

You can follow along this guide to create the test suite, or you can clone this demo app via GitHub repo.

Prepare Tools for Testing

RSpec

RSpec is a testing framework that we choose for this demo. Let install and setup the RSpec first.

Add RSpec gem in gemfile and run bundle install to install it.

group :development, :test do
gem 'rspec-rails', '~> 3.4', '>= 3.4.2'
end

Run the RSpec install generator to install RSpec config files.

rails g rspec:install

The install generator will install the following config files:

.rspec

This file contains the default flag that will be included when run the rspec command.

spec/spec_helper.rb

This file contains the RSpec custom setting that will be loaded in every test. You can follow the RSpec suggestion to enable setting like this.

spec/rails_helper.rb

This file contains the Rails related RSpec setting that will be required explicitly in the spec file. Let remove the fixture setting like this as we will using FactoryGirl to manage our test data.

Capybara

We use Capybara to write the test for simulates the user interact with our application.

Add Capybara gem in gemfile and run bundle install to install it.

group :test do
gem 'capybara', '~> 2.6', '>= 2.6.2'
end

Add the “require” line like this in spec/rails_helper.rb file.

Database Cleaner

To avoid the old test data conflict our test result. We use database cleaner to ensure we have the clean state for testing.

Add Database Cleaner gem in gemfile and run bundle install to install it.

group :test do
gem 'database_cleaner', '~> 1.5', '>= 1.5.1'
end

Add the config setting like this in spec/support/database_cleaner.rb file.

Add the “require” line like this in spec/rails_helper.rb file.

FactoryGirl

To prepare database records for testing purpose. You can either using standard model “create” method or fixtures or factories. In this demo, we will use FactoryGirl as it’s not like the standard model “create” method which will brother the model validation. Also, it’s flexible than fixtures which allow you to modify the data without hard code it.

Add FactoryGirl gem in gemfile and run bundle install to install it.

group :development, :test do  
gem 'factory_girl_rails', '~> 4.6'
end

Add the config setting like this in spec/support/factory_girl.rb file.

Add the “require” line like this in spec/rails_helper.rb file.

Rails Test Types

Integration Test and Unit Test are the general terminologies for describing the types of test. When testing Rails application, you writing these rails tests instead: Feature Specs, Request Specs, View Specs, Helper Specs, Controller Specs, Model Specs

Feature Specs

Feature Specs is a test that simulates how the user interacts our application through the user interface. We usually start writing feature specs when developing a new feature and it’s also the typical starting point for practicing Behaviour Driven Development (BDD).

Here is the example for RSpec Feature Specs:

require “rails_helper”feature “User create lead” do
scenario “successfully” do
visit root_path
click_on “New Lead”
fill_in “Company”, with: “Acme Company”
fill_in “Email”, with: “john@acme.com
click_on “Submit”
expect(page).to have_content “Lead was successfully created.”
end
end

Let break it down and explain it one by one:

# User visits the application root path.
visit root_path
# User clicks on the link or button which named “New Lead”.
click_on “New Lead”
# A user fills in the text field or text area with “Acme Company”.
fill_in “Company, with: “Acme Company”
# A user fills in the text field or text area with “john@acme.com”.
fill_in “Email, with: “john@acme.com”
# A user just clicks the submit button to submit the form data.
click_on “Submit”
# Verify the page object have content “Lead was successfully created” or not.
expect(page).to have_content “Lead was successfully created.”

Pros

  • It’s good for testing high-level functionality.
  • It’s cover testing the whole rails stack: view, route, helper, controller, and model.
  • Suitable for testing user interaction for the truth scenario.

Cons

  • It’s slow.
  • Because it’s slow nature, not suitable for testing edge case or every false scenario.

For more info about Capybara DSL, you can visit this page.

Request Specs

Request Specs is a test that allows you to send a request and make assertions on its response. It’s similar with Feature Specs which also go through the entire Rails stack. However, instead of interact with the user interface, we can only make assertions against the response. The typical use case for Request Specs is to test API design.

Here is the example for Request Specs:

require “rails_helper”RSpec.describe “POST /api/v1/leads” do
it "create the lead" do
lead_params = attributes_for(:lead)
post "/api/v1/leads", lead_params expect(response.status).to eq 201
expect(Lead.last.company).to eq lead_params[:company]
expect(Lead.last.email).to eq lead_params[:email]
end
end

Let break it down and explain it one by one:

# FactoryGirl attributes_for method will return a hash of attributes for the lead model.
lead_params = attributes_for(:lead)
# It sends http post request to url /api/v1/leads with hash parameters.
post “/api/v1/leads”, lead_params
# Verify the response status is equal to 201 or not.
expect(response.status).to eq 201
# Verify company value from the latest record is equal to the hash value we assigned before.
expect(Lead.last.company).to eq lead_params[:company]
# Verify email value from the latest record is equal to the hash value we assigned before.
expect(Lead.last.email).to eq lead_params[:email]

Pros

  • It’s good for testing high-level functionality.
  • It’s cover testing the whole rails stack: view, route, helper, controller, and model.
  • Suitable for testing API design.

Cons

  • It’s slow compared with another unit test.
  • Because it’s slow nature, not suitable for testing edge case or every false scenario.

View Specs

View specs is a test that allows you to test the view logic. It’s usually used for testing the conditional display of information in view templates. If you have many feature specs testing similar functionality with minor view variations, you should change it to using view specs for minimizing test suite runtime.

Here is the example for View Specs:

require "rails_helper"

RSpec.describe "leads/show.html.erb" do
context "if email is not null" do
it "display mailto link" do
lead = build(:lead)
assign(:lead, lead)

render

expect(rendered).to have_selector "a[href='mailto:#{lead.email}']"
end
end
end

Let break it down and explain it one by one:

# FactoryGirl build method will return a lead instance that’s not saved.
lead = build(:lead)
# Use the lead variable to assign values to instance variables in the view.
assign(:lead, lead)
# It will render the view template from leads/show.html.erb.
render
# Verify rendered view have mailto link or not.
expect(rendered).to eq have_selector “a[href=’mailto:#{lead.email}’]”

Pros

  • It’s a kind of unit test, so it’s fast.
  • Suitable for testing view logic.

Cons

  • It’s not good for testing high-level functionality.
  • It’s just cover testing view from rails stack.

Helper Specs

Helper specs is a test that allows you to test the helper method. Since each helper method only has one functionality, so it can be easy to test without any side-effects.

Here is the example for Helper Specs:

require "rails_helper"

RSpec.describe ApplicationHelper, "#gravatar_url" do
it "display gravatar url by passing email in it" do
lead = build(:lead)
url = helper.gravatar_url(lead.email)

expect(url).to eq "http://www.gravatar.com/avatar/f8fb533340e314935afe3a1d05a9070b?s=48"
end
end

Let break it down and explain it one by one:

# FactoryGirl build method will return a lead instance that’s not saved.
lead = build(:lead)
# RSpec provides this helper object which allows us to call all helper methods via this helper object.
url = helper.gravatar_url(lead.email)
# Verify the returned url is equal to the given Gravatar url.
expect(url).to eq “http://www.gravatar.com/avatar/f8fb533340e314935afe3a1d05a9070b?s=48"

Pros

  • It’s a kind of unit test, so it’s fast.
  • Suitable for testing helper logic.

Cons

  • It’s not good for testing high-level functionality.
  • It’s just cover testing helper.

Controller Specs

Controller spec is a test that allows you to test the logic in a controller. As follow the best practices “skinny controller, fat model” style, I find myself rarely write controller spec in my test suite.

Here is the example for Controller Specs:

require "rails_helper"

RSpec.describe LeadsController do
describe "GET #show" do
it "assigns the requested lead as @lead" do
lead = create(:lead)

get :show, { id: lead.to_param }

expect(assigns(:lead)).to eq(lead)
end
end
end

Let break it down and explain it one by one:

# FactoryGirl create method will return a saved lead instance.
lead = create(:lead)
# It sends http get request to show action with lead id parameters.
get :show, { id: lead.to_param }
# Verify the lead instance variable is equal to the variable that FactoryGirl created before.
expect(assigns(:lead)).to eq(lead)

Pros

  • It sits between the unit test and integration test, so it’s still faster than feature specs.
  • Suitable for testing controller logic.

Cons

  • In most cases, we don’t use controller specs unless there is complex logic inside the controller.

Model Specs

Model Specs as the name implies it’s used for testing rails model. It’s similar to unit tests that they are used to test classes or methods. As the fast nature of model spec, it is suitable to handle edge cases.

Here is the example for Model Specs:

require 'rails_helper'

RSpec.describe Lead, ".active" do
it "return only active lead" do
active_lead = create(:lead, active: true)
non_active_lead = create(:lead, active: false)

result = Lead.active

expect(result).to eq [active_lead]
end
end

Let break it down and explain it one by one:

# FactoryGirl create method will return a saved active lead instance.
active_lead = create(:lead, active: true)
# FactoryGirl create method will return a saved non-active lead instance.
non_active_lead = create(:lead, active: false)
# Execute the active class method and assign the array records to result variable.
result = Lead.active
# Verify the result variable whether it's contains only one active lead or not.
expect(result).to eq [active_lead]

Pros

  • It’s a kind of unit test, so it’s fast.
  • Suitable for testing model logic.
  • Suitable handle edge cases.

Cons

  • It’s not good for testing high-level functionality.
  • It’s just cover testing model.

Four Phase Test Pattern

After going through all rspec examples, you may recognize there is some pattern for the writing test. When writing these rails tests, the best practice is alway follow the Four-Phase Test pattern:

test do
# setup - Prepare object for this test
# exercise - Execute the functionality we are testing
# verify - Verify the exercise's result against our expectation
# teardown - Resetting all data to pre-test state
end

By writing test with this pattern, the code will look more clear and easier to follow.

Here is the example for Model Specs with Four Phase Test pattern:

require 'rails_helper'

RSpec.describe Lead, ".active" do
it "return only active lead" do
# setup
active_lead = create(:lead, active: true)
non_active_lead = create(:lead, active: false)
# exercise
result = Lead.active
# verify
expect(result).to eq [active_lead]
# teardown
# RSpec automatically handles it.
end
end

Coming up next

For the part 3, we will explore what Stubs, Mocks and Spies means. Stay tuned!

Footnote

If you find anything in the above, that is incorrect or have feedback for this guide. Please don’t hesitate and leave your comment below.

If this help, just click ♥ to let more people find it too!

Struggle on building frontend components and integrate to Ruby on Rails? Be sure to check out UiReady — Bootstrap theme marketplaces dedicated for Rails developers.

--

--

Tim Cheung
Design & Code

AWS Certified @CloudWarriorHQ | Join our FB group http://bit.ly/2nMA7Fe for more AWS secrets.