Ruby on Rails application testing using RSpec

August 17, 2017
admin

RSpec is one of the most popular Ruby testing library. Its idea of the testing application is behavior testing rather than testing only specific methods.

FactoryGirl helps to create test data which is needed for writing tests. It builds instances of models for using in test scenarios. In factories, we define how to build the instances.

Installation and configuration:

In order to use RSpec with FactoryGirl in your rails application you have to add the following gems to your Gemfile:

group :development, :test do
  gem ‘factory_girl_rails’
  gem ‘rspec-rails’
end

Then run ‘bundle install’ from your project root path in a terminal window.

You can install the gems manually from your shell, by running:

gem install factory_girl_rails
gem install rspec-rails

Run the following command to initialize the spec/ directory (where specs will reside):

rails generate rspec:install

It will add the following files which will be used for rspec configuration:
.rspec
spec/spec_helper.rb
spec/rails_helper.rb

Use the ruby on rails RSpec command to run your specs:

bundle exec rspec

By default, the above command will run all *_spec.rb files under spec directory.
You can run only a subset or a single spec file by the following command:

# Run model specs
bundle exec rspec spec/models

# Run user model specs
bundle exec rspec spec/models/user_spec.rb

# Run specs for ProductsController
bundle exec rspec spec/controllers/products_controller_spec.rb

# Run spec on line 7 of ProductsController
bundle exec rspec spec/controllers/products_controller_spec.rb:7

To include all the default strategies provided (#build, #create, #build_stubbed and #attributes_for), as well as the complimentary *_list methods for FactoryGirl, add the following code to your spec/support/factory_girl.rb directory:

# spec/support/factory_girl.rb
RSpec.configure do |config|
 Config.include FactoryGirl::Syntax::Methods
end

Enable the autoloading of the support directory by uncommenting the following line in your spec/rails_helper.rb:

Dir[Rails.root.join(‘spec/support/**/*.rb’)].each { |f| require f }

Model testing:

RSpec model spec entirely tests the state and behavior of an object. A model spec ensures the proper functioning of a class and its associated relations, as a real-world application will contain innumerable class containing associations with other classes.

Shoulda Matchers is a gem, providing one-liner common Rails functionality tests. After adding shoulda-matchers in Gemfile, include, require ‘shoulda/matchers’ in rails_helper.rb file, in order to access it in model specs.

require ‘rails_helper’

RSpec.describe Sector, type: :model do
 context 'validations' do
   it { should validate_presence_of(:name) }
   it { should allow_nil(:address) }
 end

 context 'associations' do
   it { should belong_to(:industry) }
   it { should have_many(:sub_sectors).dependent(:restrict_with_exception) }
 end
end

For validation, we can also call various shoulda-matchers methods such as, validate_numericality_of, validate_uniqueness_of, allow_value, allow_blank, ensure_inclusion_of, ensure_length_of, validate_acceptance_of (for boolean values), validate_confirmation_of (matches two values), etc.

Similarly, for association we can also use, have_and_belong_to_many, accept_nested_attributes_for, etc.

Controller testing:

Along with CRUD operations, a controller contains various methods that need to be tested in order to ensure a rigid and well-designed application. For any application that contains user roles, the login must be done at the beginning of a controller spec.

The login is done using Rails Device and UserFactory is setup in the following way. ‘confirmed_at’ should be added, if the user account needs a verification to sign in.

FactoryGirl.define do
  factory :user do
  sequence(:email) { |n| "user#{n}@example.com" }
  name 'TestName'
  password '123456'
  password_confirmation '123456'
  role 'admin'
  confirmed_at Date.today
  end
end

Generate controller_macros.rb file inside spec/support, which contains the method for signing in and later this is called before every controller test.

module ControllerMacros
  def login_user
   before(:each) do
    @request.env["devise.mapping"] = Devise.mappings[:user]
    user = FactoryGirl.create(:user)
    user.confirm! #only if account is confirmable
    sign_in user
   end
  end
end

Now in spec/rails_helper.rb, add the following lines:

require 'spec_helper'
require 'rspec/rails'
require 'devise'

RSpec.configure do |config|
 config.include Devise::Test::ControllerHelpers, :type => :controller
 config.extend ControllerMacros, :type => :controller
end

The login is done once before any request for a controller spec. Assuming a factory of the controller to be tested has already been generated first, the controller spec is then written.

GET request: Tests for GET request of an index and new methods can be very simple as, checking the number of objects contained in the index page. For edit and show operations, an object is created using FactoryGirl and passed along the parameter.

RSpec.describe ProductsController, type: :controller do
 describe 'Products controller request specs' do
  login_user
 
  context 'GET #index' do 
   it 'should success and render to index page' do
      get :index
      expect(response).to have_http_status(200)
      expect(response).to render_template :index
   end
  end

  context 'GET #show' do
   let!(:product) { create :product }
 
  it 'should success and render to edit page' do
     get :show, params: { id: product.id }
     expect(response).to have_http_status(200)
     expect(response).to render_template :edit
    end
   end 
  end
 end

POST request: The post request for create operation can be checked by an increment in the object count.

context 'POST #create' do
 let!(:product) { FactoryGirl.create :product }
 
it 'create a new product' do
  params = {
   name: 'An awesome product',
   price: 50
  }
  expect { post(:create, params: { product: params }) }.to change(Product, :count).by(1)
  expect(flash[:notice]).to eq 'Product was successfully created.'
 end
end

PUT request: The update function uses the put request, where the object is first created using FactoryGirl. The values of the object are then updated by a local variable. The test is then verified by reloading the object and checking its field values from the local variable.

context 'PUT #update' do
 let!(:product) { create :product }
 
 it 'should update product info' do
   params = {
    name: 'Awesome product',
    price: 45
   }

   put :update, params: { id: product.id, product: params }
   product.reload
   params.keys.each do |key|
    expect(product.attributes[key.to_s]).to eq params[key]
   end
  end
 end

DELETE request: The delete request takes the object to be deleted as a parameter and it can be tested by a decrement in object count.

One of the most basic tests for each operation is to check if the correct template is rendered for each operation. After completion of each request, redirect_to the desired page can also be checked.

context 'DELETE #destroy' do
 let!(:product) { create :product }

it 'should delete product' do
   expect { delete :destroy, params: { id: product.id } }.to change(Product, :count).by(-1)
   expect(flash[:notice]).to eq 'Product was successfully deleted.'
  end
 end

Routes testing:
RSpec routes testing is of much importance to classes having nested attributes. Route tests for simple CRUD operations are pretty simple.

RSpec.describe ProductsController, type: :routing do
 describe 'routing' do

  it 'routes to #index' do
   expect(get: '/products').to route_to('products#index')
  end

  it 'routes to #show' do
   expect(get: '/products/1').to route_to('products#show', id: '1')
  end

  it 'routes to #new' do
   expect(get: '/products/new').to route_to('products#new')
  end
 
  it 'routes to #update via PUT' do
   expect(put: '/products/1').to route_to('products#update', id: '1')
  end
  
  it 'routes to #update via PATCH' do
   expect(patch: '/products/1').to route_to('products#update', id: '1')
  end
 end
end

Test codes act as a safeguard for applications gaining complexities each day with more modifications and updates. RSpec and FactoryGirl help speed up in writing the test codes. It also helps new developers avoid predestined bugs in existing applications.

We tried to provide as much details as possible to cover rails application testing with RSpec and FactoryGirl. To get a better understanding of how  Ruby on Rails RSpec and FactoryGirl works, you can read the Getting Started with RSpec and Getting started with FactoryGirl

Contributor: Mehreen Mansur & Shafiqul KaderNascenia

No comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.