文系人間がエンジニアを目指すブログ

大学の文系学部を卒業した私が、ソフトウェアエンジニアになって、一人前を目指す過程を書くブログです。

【Rspec】ネストされているコントローラーのテストについて。「No route matches」と言わせない書き方

この記事を書いた背景

題名の通りですが、ネストされているコントローラーのテストをRspecで書いていて、ややハマったので、書きました。

ネストの状況はこの通り。

Rails.application.routes.draw do
  devise_for :users
  root 'groups#index'
  resources :users, only: [:edit, :update]
  resources :groups, except: [:show, :destroy] do
    resources :messages, only: [:index, :create]
  end
end

今回テストしようとしていたのは、messagesコントローラーです。

ご覧の通り、groupsの下にネストされているので、indexとcreateのroutingが、

GET    /groups/:group_id/messages(.:format) messages#index
POST   /groups/:group_id/messages(.:format) messages#create

このようになっています。

そのため、messageをテストするにも、親となるgroupのidが必要になってくるのですね。

そこの書き方を示していきたいと思います。

開発環境

開発環境は以下の通り。

ruby '2.3.1'
'rails', '~> 5.1.6'
'rspec-rails' '3.7.2'
'factory_girl_rails', "~> 4.4.1"
'devise'

ネストされているコントローラーのテストをRspecで書く(indexアクション)

indexアクションのテストを例に説明していきます。

想定通りのインスタンスが取得できているか、レンダリング先は正しいか、ということをテストします。

まずは全体像を示しておきます。

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 user
        get :index, params: { group_id: group.id }
      end

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

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

      it 'redners index' do
        expect(response).to render_template :index
      end
    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

つらつらと書いていますが、一番言いたいのはココ!

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

beforeの記述部分の、「get :index, params: { group_id: group.id }」というところです。

なぜこんな書き方なのか?

もしネストしていないモデルのコントローラーだったら、

get :index

これだけで擬似的にアクションを呼び出すことが可能です。

しかし、ネストされているので、親モデルのidがパラメーターとして必要になります。

ゆえに、

params: { group_id: group.id }

で呼び出してあげる必要があるのでした〜。

注意点

ネストしていないモデルで、かつパラメータとしてidが必要なアクションがありますよね。例えば、showアクションとか。

それを呼び出すときは、

#gemのfactory_girl_railsを使用
group = create(:group)

get :show, id: group

こんな風に書きます。

筆者は、先ほどのネストされたモデルのアクションを呼び出すときに、同じノリで書いてしまい、プチハマりしました

group = create(:message)
get :index, group_id: group

こんな風に。

そうすると、「No route matches」と言われ続けます。

皆さんは筆者と同じ轍を踏まないようにしてくださいね(^^;)