テストを実装してみる(2)コントローラー編

前回に引き続き、

アプリケーションのテストを行います。

 

今回はコントローラー編!ということで、

メッセージ送信機能のコントローラーが正しく機能しているかを調べます。

メッセージ送信機能が動いているかを調べるにあたって今回は、

送信したあとのindexアクションが動作しているかをテストしてみます。

 

deviseをRSpecで使用するように設定する

コントローラーでのテストの場合、ログインしてるか、してないかによってコントローラーアクションが変わってくることがあります。

 

そこで

ログイン状態ログインしていない状態を分けてテストできるように設定をします。
ログインをしている、していないということはgemのdeviseに任せているので、

deviseRSpecで使用できるように設定する必要があります。

 

やりかたはこのサイトを参考にしました〜

qiita.com


⑴まず、/spec/supportディレクトリに、controller_macros.rbを作成し、ログインのためのloginメソッドを定義する。

 

module ControllerMacros
  def login(user)
    @request.env["devise.mapping"] = Devise.mappings[:user]
    sign_in user
  end
end

 (これはgemの中に内臓されている文で深く理解するのは難しいらしいです。なので私は定型文として捉えました)

その後、rails_helper.rbに、deviseのコントローラのテスト用のモジュールと、いま定義したControllerMacrosを読み込む記述を行います。

 

RSpec.configure do |config|
  Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
  config.include Devise::Test::ControllerHelpers, type: :controller
  config.include ControllerMacros, type: :controller
  #〜省略〜
end

 

(これも定型文のような認識。今の段階でこれを一から書くのはなかなか難しい、、。) 

 

この2つの記述によって、

とりあえずcurrent_userなど、deviseのヘルパーメソッドを使用したインスタンス変数のテストが出来るようになりました!

 

テストコードを書いていく

ファイルを作成する

この作業は基本的にモデルのテストのときと同じです。

テストしたいメッセージコントローラーのファイルを作成するために、

controllersディレクトリを作ってその中にmessages_controller_spec.rbというファイル名をファイル命名規則に従って書いていきます(前回説明済み)

 

基本コードを書く

これもモデルのテストの時と同じですね。

 

違うのは今回の場合分けは「ログインしているか」「ログインしてないか」の2通りになるということです。

 

場合分けにはcontextを使うんでしたよね!

indexアクションとcreateアクションで分けて、

それぞれログインしているときとログインしてないときにcontextを分けます。

 

f:id:lilybelly:20181009151531p:plain

 

 

indexアクションについてテストコードを書く

まず大まかに概要を書きます!

 

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

  • ログインしている場合
    • アクション内で定義しているインスタンス変数があるか
    • 該当するビューが描画されているか
  • ログインしていない場合
    • 意図したビューにリダイレクトできているか
ログインしている場合のテストコードを書いていく 

 

 

何度も使うことを最初に定義する

 

今回はログインしている状態を記述しようとします。

ログインしている中でも、

  • アクション内で定義しているインスタンス変数があるか
  • 該当するビューが描画されているか

の2つを調べるんでしたね。

 

このときどちらも前提条件として「ログインをする」、「擬似的にindexアクションを動かすリクエストを行う」共通の処理にあるので、

処理を先にまとめて書いてしまう方がコードがすっきりします。

 

先にまとめる方法としてbeforeメソッドを使いましょう。

before do
        login user
        get :index, params: { group_id: group.id }
      end

 

簡単に解説すると

ログインをする状態を表すには

さっきdeviseで設定した「login」を呼び出せばいいです。

module ControllerMacros
  def login(user)
    @request.env["devise.mapping"] = Devise.mappings[:user]
    sign_in user
  end
end

 

「login」で定義できてるので、

2行目の login userでオッケーです。

 

3行目は「擬似的にindexアクションを動かすリクエストを行う」ために、getメソッドを利用しています。messagesのルーティングはgroupsにネストされているため、group_idを含んだパスを生成します。そのため、getメソッドの引数として、params: { group_id: group.id }を渡しています。

 

ここまでで、共通の処理を定義することができました。

 

アクション内で定義しているインスタンス変数があるかどうか確かめる

 アクション内で定義しているインスタンス変数があるかどうか確かめていきましょう。
インスタンス変数に代入されたオブジェクトは、コントローラのassigns メソッド経由で参照できます。


@messageを参照したい場合、assigns(:message)と記述することができます。

 

it 'assigns @message' do
        expect(assigns(:message)).to be_a_new(Message)
      end

      it 'assigns @group' do
        expect(assigns(:group)).to eq group
      end

 

@messageはMessage.newで定義された新しいMessageクラスのインスタンスです。

be_a_newマッチャを利用することで、 対象が引数で指定したクラスのインスタンスかつ未保存のレコードであるかどうか確かめることができます。

今回の場合は、assigns(:message)が

Messageクラスのインスタンスかつ未保存かどうかをチェックしています。

 

 

it 'assigns @group' do
        expect(assigns(:group)).to eq group
      end
 

@groupはeqマッチャを利用して参照したassigns(:group)とgroupが同一であることを確かめることでテストできます。

 

該当するビューが描画されているかどうかをテストしていきましょう。

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

 

 

expectの引数にresponseを渡しています。responseは、example内でリクエストが行われた後の遷移先のビューの情報を持つインスタンスです。 render_templateマッチャは引数にアクション名を取り、引数で指定されたアクションがリクエストされた時に自動的に遷移するビューを返します。この二つを合わせることによって、example内でリクエストが行われた時の遷移先のビューが、indexアクションのビューと同じかどうか確かめることができます。

 

 

ログインしていない場合を考える
it 'redirects to new_user_session_path' do
  expect(response).to redirect_to(new_user_session_path)
end

 

redirect_toマッチャは引数にとったプレフィックスにリダイレクトした際の情報を返すマッチャです。

今回の場合は、非ログイン時にmessagesコントローラのindexアクションを動かすリクエストが行われた際に、ログイン画面にリダイレクトするかどうかを確かめる記述になっています。

何度も使う変数をまとめる

 

テスト内で何度も使用する変数はletメソッドを使用するらしいです。

 

qiita.com

このサイトにletの書き方も書いてありました。

 

let(:foo) { ... } のように書くと、 { ... } の中の値が foo として参照できる、というのが letの基本的な使い方です。

 

 

let(:group) { create(:group) }
let(:user) { create(:user) }

 

上は

create(:group)の値が :group として参照できるということと

create(:user)の値が :user として参照できるということ

 

を表しています。

これをコードの一番上にまとめとくことで、

「:group」でgroupのインスタンス変数を

「:user」でuserのインスタンス変数を扱うことができます。

 

 

 

完成したコード

 

ちょっとわかりにくかったので、

コメントアウトでどんな風に動いているのかを簡単に書いておきます。

 

require 'rails_helper'

describe MessagesController do
  let(:group) { create(:group) }
  let(:user) { create(:user) }
  #コードをまとめて書いている
  describe '#index' do

    context 'log in' do #ログインしてる場合
      before do
        login user #ログインしててindexアクションを動かすことをまとめてる
        get :index, params: { group_id: group.id }
      end

      it 'assigns @message' do
        expect(assigns(:message)).to be_a_new(Message)
      end #参照した:messageが保存できてるか確かめる

      it 'assigns @group' do
        expect(assigns(:group)).to eq group
      end #参照したgroupとgroupが一緒か確かめる

      it 'redners index' do
        expect(response).to render_template :index
      end #indexのビューに移動できてるか確かめる
    end

    context 'not log in' do
      before do
        get :index, params: { group_id: group.id }
      end

      it 'redirects to new_user_session_path' do
        expect(response).to redirect_to(new_user_session_path)
      end #ログインしてない場合はログイン画面にリダイレクトされるか確かめる
    end
  end
end

 

 

 

 今日はこんな感じです。

ちょっと難しくて、自分でもこんな感じなのかと理解するので精一杯でした!

 

説明がわかりにくくてすみません。

またちゃんと理解できたときに編集を更新したいと思います!