Skip to content

Latest commit

 

History

History

testing

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

Testing with RSpec

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-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.

Adding rspec-rails to Your Project

  1. Add rspec-rails to your Gemfile in the development and test groups:
#
# Gemfile
#
 group :development, :test do
   gem 'rspec-rails'
 end
  1. Run bundle install (or bundle for short) in your terminal so that rspec-rails is actually added to your project.

  2. 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.

  1. Configure your specs by going into the .rspec file and removing the line that says --warnings if there is one.

  2. 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

Running rspec-rails Tests

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.

Writing rspec-rails Tests

Testing Models

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

Testing Controllers

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

Testing Views

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!

Challenges

Fork and clone the rspec_testing app. You will need to add the FFaker gem to your project to complete these exercises.