テストを実装してみる(2)コントローラー編
前回に引き続き、
アプリケーションのテストを行います。
今回はコントローラー編!ということで、
メッセージ送信機能のコントローラーが正しく機能しているかを調べます。
メッセージ送信機能が動いているかを調べるにあたって今回は、
送信したあとのindexアクションが動作しているかをテストしてみます。
deviseをRSpecで使用するように設定する
コントローラーでのテストの場合、ログインしてるか、してないかによってコントローラーアクションが変わってくることがあります。
そこで
ログイン状態とログインしていない状態を分けてテストできるように設定をします。
ログインをしている、していないということはgemのdeviseに任せているので、
deviseをRSpecで使用できるように設定する必要があります。
やりかたはこのサイトを参考にしました〜
⑴まず、/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を分けます。
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メソッドを使用するらしいです。
このサイトに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
今日はこんな感じです。
ちょっと難しくて、自分でもこんな感じなのかと理解するので精一杯でした!
説明がわかりにくくてすみません。
またちゃんと理解できたときに編集を更新したいと思います!