Objectives |
---|
Identify various aspects of Rails apps that we might want to test. |
Test model methods using rspec-rails. |
Test controller actions using rspec-rails. |
Rspec is a testing gem for Ruby. It helps us write tests that sound like user stories or planning comments ("This method should..."). <a href"https://github.com/rspec/rspec-rails" target="_blank">rspec-rails is a testing framework specifically for Rails. We'll use rspec-rails to test our models and controllers.
- Add rspec-rails to your Gemfile in the
development
andtest
groups:
#
# Gemfile
#
group :development, :test do
gem 'rspec-rails'
end
-
Run
bundle install
(orbundle
for short) in your terminal so that rspec-rails is actually added to your project. -
Add tests to your rails project using the terminal:
$ rails g rspec:install
This creates a spec
directory. It also adds spec/spec_helper.rb
and .rspec
files that are used for configuration. See those files for more information.
-
Configure your specs by going into the
.rspec
file and removing the line that says--warnings
if there is one. -
If you created models before adding rspec-rails, create a spec file for each of your models. (This is only necessary if you had a model created before you installed rspec-rails.)
$ rails g rspec:model MODEL_NAME
Typical spec folders and files for a Rails project include:
spec/models/user_spec.rb
spec/controllers/users_controller_spec.rb
spec/views/user/show.html.erb_spec.rb
To run all test specs, go to the terminal and type rspec
or bundle exec rspec
.
To run only a specific set of tests, type rspec
and the file path for the tests you want to run in the terminal:
# run only model specs
rspec spec/models
# run only specs for `ArticlesController`
rspec spec/controllers/articles_controller_spec.rb
Run rspec
from the terminal now to check that your install worked.
We can set up a @user
for testing purposes with User.create
. Wrapping this in before do
makes a @user
object available to the tests:
#
# spec/models/user_spec.rb
#
require 'rails_helper'
RSpec.describe User, type: :model do
before do
user_params = Hash.new
user_params[:first_name] = FFaker::Name.first_name
user_params[:last_name] = FFaker::Name.last_name
user_params[:email] = FFaker::Internet.email
user_params[:password] = FFaker::Lorem.words(2).join
user_params[:password_confirmation] = user_params[:password]
@user = User.create(user_params)
end
end
Assuming we've already set a @user
variable with first and last names, we can then test that the full_name
method correctly calculates the full name:
#
# spec/models/user_spec.rb
#
require 'rails_helper'
RSpec.describe User, type: :model do
...
describe "#full_name" do
it "joins first name and last name" do
expect(@user.full_name).to eq("#{@user.first_name} #{@user.last_name}")
end
end
end
To test authentication, we need to define some @current_user
before each of our tests run. The last line in this before do
block -- allow_any_instance_of(...
-- creates a "stub" (fake) current_user
instance method for the ApplicationController and sets it up as a getter that only ever returns the @current_user
we made with ffaker.
#
# spec/controllers/articles_controller_spec.rb
#
require 'rails_helper'
RSpec.describe ArticlesController, type: :controller do
before do
user_params = Hash.new
user_params[:first_name] = FFaker::Name.first_name
user_params[:last_name] = FFaker::Name.last_name
user_params[:email] = FFaker::Internet.email
user_params[:password] = FFaker::Lorem.words(2).join
user_params[:password_confirmation] = user_params[:password]
@current_user = User.create(user_params)
allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(@current_user)
end
describe "GET #index" do
it "should assign @articles" do
all_articles = Article.all
get :index
expect(assigns(:articles)).to eq(all_articles)
end
it "should render the :index view" do
get :index
expect(response).to render_template(:index)
end
end
describe "GET #new" do
it "should assign @article" do
get :new
expect(assigns(:article)).to be_instance_of(Article)
end
it "should render the :new view" do
get :new
expect(response).to render_template(:new)
end
end
describe "POST #create" do
context "success" do
it "should add new article to current_user" do
articles_count = @current_user.articles.count
post :create, article: {title: "blah", content: "blah"}
expect(@current_user.articles.count).to eq(articles_count + 1)
end
it "should redirect_to 'article_path' after successful create" do
post :create, article: {title: "blah", content: "blah"}
expect(response.status).to be(302)
expect(response.location).to match(/\/articles\/\d+/)
end
end
context "failure" do
it "should redirect to 'new_article_path' when create fails" do
# create blank article (assumes validations are set up in article model for presence of title and content)
post :create, article: { title: nil, content: nil}
expect(response).to redirect_to(new_article_path)
end
end
end
end
We could use a tool like Capybara to test client-side views and interactions (e.g. does clicking on "Logout" do what we expect?). We won't cover view testing today, though!
Fork and clone the rspec_testing app. You will need to add the FFaker gem to your project to complete these exercises.