コントローラのテストで難しい点を解説 【rspec】【初学者用】
この記事ではチャットアプリでテキストの送信機能のテストについて見ていきます。
コントローラーのテストコードの完全な入り口ではなく基本の事前理解が必要です。
messagesコントローラーファイルとそれに対する(完成した)テストコードです。一度目を通してください。app/controller/messages_controller.rb
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を触って間もない方は、一見難しそうに見えますが
ここで読み解いていないテストコードは、一つ一つ見ていけば意外とシンプルで最初の基本に沿って読み解くことができますね。