Learn the First Best Practices for Rails and RSpec
Learn the First Best Practices for Rails and RSpec
January 11, 2016
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
* 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.
阅读量: 808
发布于:
修改于:
发布于:
修改于: