Help us understand the problem. What is going on with this article?

RSpec初心者のdevise認証システムテスト

はじめに

RSpecの機能や構文を全く知らない状態からユーザー登録や編集のRequest specでのテストを行った際の備忘録です。
初心者なので細かい記述や命名は参考にしないことをお勧めします。

環境

・Rails 5.2.4.1
・Ruby 2.6.5
・rspec-rails 3.9.0
・factory_bot_rails 5.1.1
deviseで登録/ログインシステムを構築しています。

RSpecとFactory_botの導入についてはこちらが参考になりました。
参考記事:RailsアプリへのRspecとFactory_botの導入手順

事前準備

ログインヘルパーを使えるようにしておく

テスト環境でログインを実行するためにはヘルパーを用意する必要があります。
rails_helper.rbにコンフィグを追加することにより、spec/requests/以下でsign_inヘルパーを使えるようにしておきます。

rails_helper.rb
...
RSpec.configure do |config|
  config.include Devise::Test::IntegrationHelpers, type: :request #sign_inヘルパーを提供してくれます
  config.include FactoryBot::Syntax::Methods #ついでにFactoryBotもincludeしておきます
end

これでRequestテストの際にsign_inヘルパーが使えるようになりました。

factoriesフォルダでテストデータを作る

factoriesフォルダ内でFactory_botを使ってテストに使うユーザーのデータを作成します。
emailにはデータを生成する毎に通し番号をふってユニークな値を作るようにしています。

spec/factories/users.rb
FactoryBot.define do
  factory :user do #factory :testuser, class: User do のようにクラスを明示すればモデル名以外のデータも作れます。
    name { "test" }
    sequence(:email) { |n| "TEST#{n}@example.com" }
    password { "testuser" }
  end
end

このようにテストデータを定義することでspecテスト内でテスト用データを生成することができるようになります。

# DBへレコード生成
@user = create(:user)

# モデルのみの作成
@user = build(:user)

# userを生成しておく
let(:user) { create(:user) }

# ハッシュとして使えるパラメータuser_paramsを生成しておく
let(:user_params) { attributes_for(:user) }

なお、先ほど config.include FactoryBot::Syntax::Methods を追加したことでFactoryBot.と指定せずにcreateなどが使えるようになっています。

テストの基本構造

describeでテストをグループ化、contextで条件を分け、itで条件ごとの結果を検査する、という流れがRSpecテストの基本構造です。
結果の検査にはexpectという機能を使って期待する値と実際の結果が等しいかを比較します。
例を載せておきます。

RSpec.describe User do
 describe '#greet' do
   context '12歳以下の場合' do
     it 'ひらがなで答えること' do
       user = User.new(name: 'たろう', age: 12)
       expect(user.greet).to eq 'ぼくはたろうだよ。'
     end
   end
   context '13歳以上の場合' do
     it '漢字で答えること' do
       user = User.new(name: 'たろう', age: 13)
       expect(user.greet).to eq '僕はたろうです。'
     end
   end
 end
end

RSpecの基礎に関しては以下の記事が非常に参考になります。
引用:使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」

Request specを書いていく

deviseで生成されたアクションの中からいくつか書いていきます。

POST #create

spec/requests/devise_users_spec.rb
require 'rails_helper'

RSpec.describe "UserAuthentications", type: :request do
  let(:user) { create(:user) }
  let(:user_params) { attributes_for(:user) }
  let(:invalid_user_params) { attributes_for(:user, name: "") }

  describe 'POST #create' do
    before do
      ActionMailer::Base.deliveries.clear
    end
    context 'パラメータが妥当な場合' do
      it 'リクエストが成功すること' do
        post user_registration_path, params: { user: user_params }
        expect(response.status).to eq 302
      end

      it '認証メールが送信されること' do
        post user_registration_path, params: { user: user_params }
        expect(ActionMailer::Base.deliveries.size).to eq 1
      end

      it 'createが成功すること' do
        expect do
          post user_registration_path, params: { user: user_params }
        end.to change(User, :count).by 1
      end

      it 'リダイレクトされること' do
        post user_registration_path, params: { user: user_params }
        expect(response).to redirect_to root_url
      end
    end

    context 'パラメータが不正な場合' do
      it 'リクエストが成功すること' do
        post user_registration_path, params: { user: invalid_user_params }
        expect(response.status).to eq 200
      end

      it '認証メールが送信されないこと' do
        post user_registration_path, params: { user: invalid_user_params }
        expect(ActionMailer::Base.deliveries.size).to eq 0
      end

      it 'createが失敗すること' do
        expect do
          post user_registration_path, params: { user: invalid_user_params }
        end.to_not change(User, :count)
      end

      it 'エラーが表示されること' do
        post user_registration_path, params: { user: invalid_user_params }
        expect(response.body).to include 'prohibited this user from being saved'
      end
    end
  end
end

postリクエストのパラメータにletで用意したものを使ったり、httpステータスからリクエストの成功を確認したり、ActionMailerの機能で認証メールが送信されたことを確認したりしています。
$ rspec コマンドを実行し、テストが通りました。

GET #edit

spec/requests/users_autentications_spec.rb
  ...
  describe 'GET #edit' do
    subject { get edit_user_registration_path }
    context 'ログインしている場合' do
      before do
        user.confirm
        sign_in user
      end
      it 'リクエストが成功すること' do
        is_expected.to eq 200
      end
    end
    context 'ゲストの場合' do
      it 'リダイレクトされること' do
        is_expected.to redirect_to new_user_session_path
      end
    end
  end
  ...

注意点

deviseでメール認証機能をつけている場合はサインインの前に user.confirm を行い認証を済ませておく必要があります。

GET #show

これはdeviseによる生成ではなく自作したアクションです。

エラーの捉え方

get "/users/#{user_id}" で存在しないユーザーのプロフィールを見ようとした場合、エラーが発生したとします。

spec/requests/users_autentications_spec.rb
  ...
  describe 'GET #show' do
    ...
    context 'ユーザーが存在しない場合' do
      it 'エラーが発生すること' do
        user_id = user.id
        user.destroy
        expect{ get "/users/#{user_id}" }.to raise_error ActiveRecord::RecordNotFound
      end
    end
  end
  ...

上のコードではexpectに渡すgetリクエストを{}で囲んでブロックとしているため、検査対象がブロック内の返り値となりエラーを捉えることができます。
普通にexpect()として渡そうとした場合はexpectメソッドに渡される前にgetリクエストが実行されエラーが発生するため検査することが出来ません。

参考記事:RSpec でエラーを捉えらんないアレなミス

最後に

今回は認証システムのRequest specを通じてRSpecの基礎について学習しましたが、この他にも様々な機能や構文があるので引き続き学んでいこうと思います。

余談ですが最初IntegrationHelpersの存在に気付かずsupportフォルダでsign_inヘルパーを自作するという遠回りをしてしまいました。。。
参考記事:type: :requestのテストでsign_in/sign_outする
     Rails5でコントローラのテストをController specからRequest specに移行する

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away