0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Rails】RSpecを利用したコントローラー単体テスト

Posted at

はじめに

コントローラーの単体テストやRSpecの利用方法を勉強中です。
メモも兼ねてQiitaに投稿しました。

実行環境

  • rails 6.0.0
  • ruby 2.5.6
  • RSpec 4.0.0

RSpecのインストール

テストコードはRSpec,Factorybotを利用するため、以下をインストール

  gem 'rspec-rails', '~> 4.0.0'
  gem 'factory_bot_rails'

コントローラーの役割

テストコードを作成するにあたって、コントローラーの役割を一度振り返る。
コントローラー内のアクションがリクエストを受け取ったあと処理(データが必要であればモデル等と連携)を行い、レスポンスを返す。

なので、以下をベースにテストコードを記述します。

  • 受信したリクエストに対して適切なレスポンスを返す
  • データ処理が必要な場合はモデルと連携する
  • レスポンスを表示するのに適切なビューやリダイレクト先を選択する

テストコード記載

本記事ではGroupsというコントローラーのテストコードを記述します。

事前準備

FactoryBot作成

# spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name                  {'testuser'}
    provider              {'cognito-idp'}
    uid                   {'1111aaa-00zz-1111-0000-1a1a1a1a1a1a'}
    nickname              {'testuser'}

    trait :user_with_groups do
      after(:build) do |user|
        user.groups << create(:group)
      end
    end
  end
end

必要なカラムを記述。
アソシエーション先のGroupテーブルを纏めて作成する場合は、
trait :user_with_groupsを利用します。

# spec/factories/groups.rb
FactoryBot.define do
  factory :group do
    name                  {'testgroup'}
    summary               {'テスト用グループ'}

    trait :group_with_users do
      after(:build) do |group|
        group.users << create(:user)
      end
    end

    trait :invalid do
      name                {''}
    end

    trait :updategroup do
      name                {'updategroup'}
    end
  end
end

:invalid:updategroupはcreateやupdateアクション時に利用します。

Before

# spec/factories/groups_spec.rb
RSpec.describe "Groups", type: :request do
let(:user)  { FactoryBot.create(:user, :user_with_groups) }
  before do
    allow_any_instance_of(ActionDispatch::Request).to receive(:session).and_return({ user_id: user.id })
  end

Groupsコントローラーはsign_inが前提となるため、ユーザーを作成。
:user_with_groupsを指定して、関連するグループも作成しておきます。
allow_any_instance_ofでダミーのsessionメソッドを作成します。

GET #index

# spec/factories/groups_spec.rb
describe 'GET #index' do
    before do
      get groups_path
    end
    it 'indexアクションにリクエストすると正常にレスポンスが返ってくる' do
      expect(response.status).to eq(200)
    end
    it 'index.html.erbにユーザー名が表示される' do
      expect(response.body).to include 'testuser'
    end
  end

HTTPステータスコードと、ビューファイルにユーザー名が表示されているかを確認

GET #new

# spec/factories/groups_spec.rb
describe 'GET #new' do
    before do
      get new_group_path
    end
    it 'newアクションにリクエストすると正常にレスポンスが返ってくる' do
      expect(response.status).to eq(200)
    end
  end

indexと同様、HTTPステータスコードを確認

POST #create

# spec/factories/groups_spec.rb
  describe 'POST #create' do
    context '有効なパラメーターの場合' do
      let(:group_params) { FactoryBot.attributes_for(:group, :group_with_users) }
      it 'createアクションにリクエストすると正常にリダイレクトされる' do
        post groups_path, params: { group: group_params }
        expect(response.status).to eq(302)
      end
      it 'テーブルに新しいデータが登録される' do
        expect{
          post groups_path, params: { group: group_params }
        }.to change(Group, :count).by(1)
      end
      it 'indexアクションにリダイレクトされる' do
        post groups_path, params: { group: group_params }
        expect(response).to redirect_to(groups_path)
      end
    end

    context '無効なパラメーターの場合' do
      let(:group_params) { FactoryBot.attributes_for(:group, :group_with_users, :invalid) }
      it 'createアクションにリクエストすると正常にレスポンスが返ってくる' do
        post groups_path, params: { group: group_params }
        expect(response.status).to eq 200
      end
      it 'テーブルに新しいデータが登録されない' do
        expect{
          post groups_path, params: { group: group_params }
        }.to change(Group, :count).by(0)
      end
      it 'new.html.erbにエラーが表示される' do
        post groups_path, params: { group: group_params }
        expect(response.body).to include 'can&#39;t be blank'
      end
    end
  end

HTTPステータス、テーブルの更新有無、リダイレクト先、エラーの表示を確認。
FactoryBot.attributes_forでハッシュパラメーターを生成。
無効なパラメーターの場合は、traitで定義した:invalidを呼び出してnameを空で作成。

GET #show

# spec/factories/groups_spec.rb
describe 'GET #show' do
    before  do
      get group_path(group_id)
    end
    context '参加しているグループが指定された場合' do
      let(:group_id) { user.groups.ids[0] }
      it 'showアクションにリクエストすると正常にレスポンスが返ってくる' do
        expect(response.status).to eq(200)
      end
      it 'show.html.erbにグループ名が表示される' do
        expect(response.body).to include 'testgroup'
      end
    end
    context '参加していないグループが指定された場合' do
      let(:group_id) { 2 }
      it 'showアクションにリクエストすると正常にリダイレクトされる' do
        expect(response.status).to eq(302)
      end
      it 'indexアクションにリダイレクトされる' do
        expect(response).to redirect_to(groups_path)
      end
    end
    context '存在しないグループが指定された場合' do
      let(:group_id) { 999999 }
      it 'showアクションにリクエストすると正常にリダイレクトされる' do
        expect(response.status).to eq(302)
      end
      it 'indexアクションにリダイレクトされる' do
        expect(response).to redirect_to(groups_path)
      end
    end
  end

HTTPステータス、グループ名の表示、リダイレクト先を確認。
contextに応じてgroup_idを変えています。
user.groups.ids[0]はgroups_spec.rbの冒頭で関連するテーブルを作成済なので、idを代入してます。
存在しないグループが指定された場合のケースは、ActiveRecord::RecordNotFoundの例外処理で、groups_pathへリダイレクトする様にしているので、それを想定したテストとなります。

GET #edit

# spec/factories/groups_spec.rb
describe 'GET #edit' do
    before do
      get edit_group_path(group_id)
    end
    context '参加しているグループが指定された場合' do
      let(:group_id) { user.groups.ids[0] }
      it 'editアクションにリクエストすると正常にレスポンスが返ってくる' do
        expect(response.status).to eq(200)
      end
      it 'edit.html.erbにユーザー名が表示される' do
        expect(response.body).to include 'testuser'
      end
      it 'edit.html.erbにグループ名が表示される' do
        expect(response.body).to include 'testgroup'
      end
    end
    context '参加していないグループが指定された場合' do
      # 省略
    end
    context '存在しないグループが指定された場合' do
      # 省略
    end
  end

他のアクション同様、HTTPステータス、ユーザー名、グループ名の表示、リダイレクト先を確認。
# 省略部分はshowアクションと同様です。

PUT #update

# spec/factories/groups_spec.rb
describe 'PUT #update' do
    context '有効なパラメーターの場合' do
      let(:group_id) { user.groups.ids[0] }
      it 'updateアクションにリクエストすると正常にリダイレクトされる' do
        put group_path(group_id), params: { group: FactoryBot.attributes_for(:group, :updategroup) }
        expect(response.status).to eq(302)
      end
      it 'テーブルが更新される' do
        expect{
          put group_path(group_id), params: { group: FactoryBot.attributes_for(:group, :updategroup) }
        }.to change { Group.find(group_id).name }.from('testgroup').to('updategroup')
      end
      it 'showアクションにリダイレクトされる' do
        put group_path(group_id), params: { group: FactoryBot.attributes_for(:group, :updategroup) }
        expect(response).to redirect_to(group_path(group_id))
      end
    end
    context '無効なパラメーターの場合' do
      let(:group_id) { user.groups.ids[0] }
      it 'updateアクションにリクエストすると正常にレスポンスが返ってくる' do
        put group_path(group_id), params: { group: FactoryBot.attributes_for(:group, :invalid) }
        expect(response.status).to eq(200)
      end
      it 'テーブルが更新されない' do
        expect{
          put group_path(group_id), params: { group: FactoryBot.attributes_for(:group, :invalid) }
        }.to_not change(Group.find(group_id), :name)
      end
      it 'edit.html.erbにエラーが表示される' do
        put group_path(group_id), params: { group: FactoryBot.attributes_for(:group, :invalid) }
        expect(response.body).to include 'can&#39;t be blank'
      end
    end
    context '参加していないグループが指定された場合' do
      # 省略
    end
    context '存在しないグループが指定された場合' do
      # 省略
    end
  end

createアクションとほぼ同じで、HTTPステータス、テーブルの更新有無、リダイレクト先、エラーの表示を確認。
contextに応じて:updategroup:invalidを利用してハッシュパラメーターを作成し、リクエストしてます。
# 省略部分はshow,editアクションと同様です。

要確認

  • allow_any_instance_of(ActionDispatch::Request).to receive(:session).and_return({ user_id: user.id })
  • allow_any_instance_ofの利用は非推奨らしい。
  • SessionsHelper内で作成したsign_inメソッドを利用する想定でしたが、sessionのNoMethoderrorがでてしまったため、このやり方に行きつきました。
  • 他の方法がないか、継続調査。
  • destroyのテスト
    • 今回は削除の要件がなく実施しておりませんが、必要なタイミングで調べたい。
  • createとupdateで一部冗長な記述があるのでリファクタリングする。

参考記事

https://qiita.com/blueberrystream/items/884d0d23caf50b6553a0
https://qiita.com/jnchito/items/42193d066bd61c740612
https://qiita.com/t2kojima/items/ad7a8ade9e7a99fb4384

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?