コントローラのテストで難しい点を解説 【rspec】【初学者用】

この記事ではチャットアプリでテキストの送信機能のテストについて見ていきます。

コントローラーのテストコードの完全な入り口ではなく基本の事前理解が必要です。

messagesコントローラーファイルとそれに対する(完成した)テストコードです。一度目を通してください。app/controller/messages_controller.rb

 
class MessagesController < ApplicationController

before_action :set_group_users

def index
@message = Message.new
@messages = @group.messages.includes(:user)
end

def create
@message = @group.messages.new(message_params)
 
if @message.save
redirect_to group_messages_path(@group), notice: "メッセージが送信されました"
else
@messages = @group.messages.includes(:user)
flash.now[:alert] = "メッセージを入力してください"
render :index
end
end

private

def set_group_users
@group = Group.find(params[:group_id])
@users = @group.users
end

def message_params
params.require(:message).permit(:text,:image).merge(user_id: current_user.id)
end
end

spec/controllers/messages_controller_spec.rb

#〜省略〜
  describe '#create' do
    let(:params) { { group_id: group.id, user_id: user.id, message: attributes_for(:message) } }

    context 'log in' do
      before do
        login user
      end

      context 'can save' do
        subject {
          post :create,
          params: params
        }

        it 'count up message' do
          expect{ subject }.to change(Message, :count).by(1)
        end

        it 'redirects to group_messages_path' do
          subject
          expect(response).to redirect_to(group_messages_path(group))
        end
      end

      context 'can not save' do
        let(:invalid_params) { { group_id: group.id, user_id: user.id, message: attributes_for(:message, content: nil, image: nil) } }

        subject {
          post :create,
          params: invalid_params
        }

        it 'does not count up' do
          expect{ subject }.not_to change(Message, :count)
        end

        it 'renders index' do
          subject
          expect(response).to render_template :index
        end
      end
    end

    context 'not log in' do

      it 'redirects to new_user_session_path' do
        post :create, params: params
        expect(response).to redirect_to(new_user_session_path)
      end
    end
  end
end


 

それでは、躓きそうなところ(私が躓いたところ)を軸にテストコードを読み解いていきましょう。

メッセージを作成するアクションでテストすべきは以下の点です。

  • ログインしているかつ、保存に成功した場合

    • メッセージの保存はできたのか

    • 意図した画面に遷移しているか

  • ログインしているが、保存に失敗した場合

    • メッセージの保存は行われなかったか

    • 意図したビューが描画されているか

  • ログインしていない場合

    • 意図した画面にリダイレクトできているか

 

用語の説明

context

contextは、テスト対象のメソッドをどういう条件で実行するかを記載する。ここでは"ログインしている条件"や"メッセージが保存できる条件"のまとまりをcontextで表して、さらにネスト化している。

 

letメソッド

@paramsの様なインスタンス変数 を let という機能で置き換えることができるメソッド。複数のexampleで同一のインスタンス変数を使いたい場合に利用する。初回の呼び出し時のみ実行され流という遅延評価という特徴を持っている。

let(:hoge) {...}の様に書くと、{...}の値が hoge として参照できる。

 

subject

テスト対象となるオブジェクトが明確に一つに決まっている場合は、 subject 使ってテストコードをまとめる(DRY)することができる。

具体的には、まとめたテスト対象となるsubjectをexpect(subject).to ... の様な形で利用する。

 

 changeマッチャ 

createアクションのテストを行う際に利用し、引数が変化したかどうかを確かめるために利用できるマッチャ。例えば、change(Message, :count).by(1)と記述することによって、Messageモデルのレコードの総数が1個増えたかどうかを確かめることができる。

 

attributes_for メソッド

create、build同様FactoryBotによって定義されるメソッドだが、オブジェクトを生成せずに対象の属性をハッシュで返すという特徴がある。ここでは以下の様になる

attributes_for(:message) 

{text: "hello", image: "abcde.jpg",....}

 

難しいところを読み解く

 3行目

let(:params) { { group_id: group.id, user_id: user.id, message: attributes_for(:message) } }

 paramsのハッシュにそれぞれバリュー を入れている。messageの送信時に必要なデータのカラムは以下の3つです。

・そのメッセージがどのグループに属しているか(group.id)

・そのメッセージはどのユーザが投稿したのか(user.id)

・メッセージの内容(attributes_for(:message)

よってこの3行目は、paramsを呼び出した際にこのletメソッドの{{group_id: ....}}の値が参照できるよ、という記述になります。

 

5行目

context 'log in' do
      before do
        login user
      end

beforeブロックの内部に記述された処理は、各exampleが実行される直前に、毎回実行されます。context "log in "のまとまりの中ではユーザはログインしていることが前提条件なので、beforeブロックの中でloginメソッドを使用します。

またこのlogin メソッドは、deviseをrspecで利用できるよう設定した際に設定されているメソッドです。

 

 

特にrspecを触って間もない方は、一見難しそうに見えますが

ここで読み解いていないテストコードは、一つ一つ見ていけば意外とシンプルで最初の基本に沿って読み解くことができますね。