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 Kader , Nascenia