##はじめに
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ヘルパーを使えるようにしておきます。
...
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にはデータを生成する毎に通し番号をふってユニークな値を作るようにしています。
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
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
...
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}" で存在しないユーザーのプロフィールを見ようとした場合、エラーが発生したとします。
...
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リクエストが実行されエラーが発生するため検査することが出来ません。
##最後に
今回は認証システムのRequest specを通じてRSpecの基礎について学習しましたが、この他にも様々な機能や構文があるので引き続き学んでいこうと思います。
余談ですが最初IntegrationHelpersの存在に気付かずsupportフォルダでsign_inヘルパーを自作するという遠回りをしてしまいました。。。
参考記事:type: :requestのテストでsign_in/sign_outする
Rails5でコントローラのテストをController specからRequest specに移行する