RSpecによるコントローラのテストで苦労したところ

# RSpecによるコントローラのテスト

この記事を書いているのはプログラミング歴：1ヶ月の初心者です。
よろしくお願いします。

某学校のチャットアプリの課題で、二日間悩みに悩み、
非常に苦労したので、次のカリキュラムに進む前にまとめてみます。

とりあえず以下の方法でOKをもらいましたが、
なにぶん初心者ゆえに無駄な記述が多く、間違いもあると思います。
改善できる部分があれば教えていただけると助かります。

## GEM
    gem 'devise'
    gem 'rspec-rails'
    gem 'factory_girl_rails'
    gem 'faker'
    gem 'rails-controller-testing'

## 擬似的なログイン環境を作り出す。
まずログイン環境を作りましょう。
以下の記事が非常に参考になりました。この通りにやればできます。
[rspecのテスト環境でdeviseにログインする方法【rails】](http://qiita.com/Kohei_Kishimoto0214/items/e29e509b12a6eb484a42)

### 注意点
    #messages_controller_spec.rb
    describe MessaesController, type: :controller do 
自分の場合はtype: :controllerを入力しておらず、実行すると以下のエラーが出てしまっていました。

    #terminal
    $ rspec messages_controller_spec.rb
    => undefined method `login_user'

モデルのテストとして認識してしまっていて、モデルにはログインという概念がない為でしょうか？
詳しいことは分かりませんでしたが、type: :controllerを入れることで解決できました。
ググると同じようなエラーが出ている人がいましたが、特に参考になる記事は見つかりませんでした

## モデルのアソシエーション
今回テストするコントローラですが、モデルのアソシエーションは以下の関係になります。

    # message.rb
    belongs_to :user
    belongs_to :group
    # group.rb
    has_many :members
    has_many :users, through: :members
    has_many :messages
    # user.rb
    has_many :members
    has_many :groups, through: :members
    has_many :messages
    #member.rb(group_userの中間テーブル）
    belongs_to :user
    belongs_to :group

## ルーティングの設定
今回はgroupsコントローラにmessagesコントローラをネストさせていますので、
groups/:group_id/messagesというパスになっています。

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


## テストするコントローラ
    # messages_controller.rb
    class MessagesController < ApplicationController
      before_action :current_user_groups, only: [:index, :create]
      def index
        @message = Message.new
      end

      def create
        @message = Message.new(create_params)
        if @message.save
          redirect_to group_messages_path(params[:group_id])
        else
          flash.now[:alert] = "テキストが入力されていません"
          render :index
        end
      end

      private
      def create_params
        params.require(:message).permit(:text, :image).merge(group_id: params[:group_id], user_id: current_user.id)
      end

      def current_user_groups
        @group = Group.find(params[:group_id])
        @groups = current_user.groups.includes([:messages,:users])
        @messages = @group.messages.includes(:user)
      end
    end
## factory_girlの設定
    # factories/message.rb
    FactoryGirl.define do

      # factory :message do
        text             { Faker::StarWars.wookie_sentence }
        image            { Rack::Test::UploadedFile.new(File.join(Rails.root, 'spec/images/test.jpg')) }
        group
        user
        created_at       { Faker::Time.between(5.days.ago, 3.days.ago, :all) }
        updated_at       { Faker::Time.between(2.days.ago, 1.days.ago, :all) }
      end

    end

    # factories/group.rb
    FactoryGirl.define do
      factory :group do
        name       { Faker::Pokemon.name }
        created_at { Faker::Time.between(5.days.ago, 3.days.ago, :all) }
        updated_at { Faker::Time.between(2.days.ago, 1.days.ago, :all) }
      end
    end

    # factories/member.rb
    FactoryGirl.define do
      factory :member do
        user
        group
      end
    end

    # factories/member.rb
    FactoryGirl.define do
      pass = Faker::Internet.password(8)

      factory :user do
        name                  { Faker::Pokemon.name }
        email                 { Faker::Internet.email }
        password              pass
        password_confirmation pass

        after(:create) do |user|
          create(:member, user: user, group: create(:group))
        end
      end
    end
## ファイル構成
用意しているファイルは以下の通りです。
<img width="306" alt="スクリーンショット 2017-07-08 12.01.17.png" src="https://qiita-image-store.s3.amazonaws.com/0/187119/507a274b-9352-ced6-53d3-3c8104dfba50.png">

##　では、テストコードを書いていきましょう！
### messages#indexアクションのテスト
#### let文
factoryで用意したダミーをあらかじめletで定義しておきます。

    #messages_controller_spec.rb
    describe MessagesController, type: :controller do
      let(:user) { create(:user) }
      let(:group) { create(:group) }
      let(:message) { build(:message) }
#### form_forで使う変数@message
※以下、describe、context、itの中は、できれば英語で書きましょうということでしたので、一応英語で書いています。つたない英語ですが、よろしくお願いします。

    #messages_controller_spec.rb
    it "is assigns sg @message" do
      blank_message = Message.new
      get :index, params: { group_id: group }
      expect(assigns(:message).attributes).to eq(blank_message.attributes)
      end

    #messages_controller.rb
    @message = Message.new
以上のように書きました。（教えてもらいました。）
最初attributesなしではテストがパスしませんでした。attributesを実行すると、元の変数がハッシュの形になって返ってきます。

#### 変数@groupのテスト
    #messages_controller_spec.rb
    it "is assigns sg @group" do
      get :index, params: { group_id: group }
      expect(assigns(:group)).to eq(group)
    end

    #messages_controller.rb
    @group = Group.find(params[:group_id])
特に何も言われませんでしたが、あんまり自身ないです。

#### 変数@messagesのテスト
    #messages_controller_spec.rb
    it "is assigns pl @messages has current_group.users" do
      messages = create_list(:message, 3, user_id: user.id, group_id: group.id)
      get :index, params: { group_id: group }
      expect(assigns(:messages)).to match(messages)
    end

    #messages_controller.rb
    @messages = @group.messages.includes(:user)
グループに所属するメッセージが取得されているかテストしています。
create_listを実行する際に、factoryであらかじめ生成したuserとgroupにuser_idとgroup_idを指定して、複数のレコードを生成しました。

#### 変数@groupsのテスト
    #messages_controller_spec.rb
    it "is assings pl @groups has current_user.groups" do
      groups = create_list(:group, 3)
      groups.each do |g|
        g.members.create(user: user)
      end
      get :index, params: { group_id: groups.first.id }
      groups = user.groups
      expect(assigns(:groups)).to eq groups
    end

    #messages_controller.rb
    @groups = current_user.groups.includes([:messages,:users])

テストしたいのは、現在ログインしているユーザーと関係のあるグループが送られてきているか、です。
どうしたら中間テーブルと関係のある複数のレコードを作れるのか、これが全く分かりませんでした。
each文で中間テーブルのレコードを作成することで、解決しています。

ビューに遷移しているか？ は割愛しますが、ここまでの完成コードを載せておきます。
## ここまでの完成コード
    #messages_controller_spec.rb
      describe 'GET #index' do
        context 'when user login' do

          before do
            login_user user
          end

          it "is assigns sg @message" do
            blank_message = Message.new
            get :index, params: { group_id: group }
            expect(assigns(:message).attributes).to eq(blank_message.attributes)
          end

          it "is assigns sg @group" do
            get :index, params: { group_id: group }
            expect(assigns(:group)).to eq(group)
          end

          it "is assigns pl @messages has current_group.users" do
            messages = create_list(:message, 3, user_id: user.id, group_id: group.id)
            get :index, params: { group_id: group }
            expect(assigns(:messages)).to match(messages)
          end

          it "is assings pl @groups has current_user.groups" do
            groups = create_list(:group, 3)
            groups.each do |g|
              g.members.create(user: user)
            end
            get :index, params: { group_id: groups.first.id }
            groups = user.groups
            expect(assigns(:groups)).to eq groups
          end

          it "renders index template?" do
            get :index, params: { group_id: group }
            expect(response).to render_template :index
          end
        end
※改善点などあればぜひご指導ください。

###messages#createアクションのテスト

#### バリデーション
    # models/message.rb
    validates :text, presence: true, unless:"image?"
#### @messageがDBに保存されているか？
    #messages_controller_spec.rb
    it 'is write in Database?' do
      expect do
        post :create, params: { group_id: group, message: attributes_for(:message) }
      end.to change(Message, :count).by(1)
    end

    #messages_controller.rb
    if @message.save
      redirect_to group_messages_path(params[:group_id])
    else

書き方がよくわからなかったので、この辺からもらってきました。モデルにカウントメソッドを使って、テーブル内のレコードの数を数えているという感じでしょうか。
[Rails RSpecの基本 ~Controller編~](http://qiita.com/shizuma/items/84e07e558abd6593df15)

#### @messageがDBに保存されていないか？

    #messages_controller_spec.rb
    it 'is not write in Database?' do
      expect do
        post :create, params: { group_id: group, message: attributes_for(:message, text: nil, image: nil) }
      end.to change(Message, :count).by(0)
    end

    #messages_controller.rb
    else
      flash.now[:alert] = "テキストが入力されていません"
      render :index
    end

意外と苦労したのが、保存できないパターンを作ることです。

    #messages_controller_spec.rb
    message = build(:user, text = nil, image = nil)
てな感じで書けば行けるんじゃないかと思ったんですが、どうやっても保存されるのはこの新しく定義したmessageではなくて、letで書いていた別のmessage。
あんまりうまく行かないんで質問したところ、paramsに直接attributes_forで指定するやり方で、解決しました。

## 完成コード
    # messages_controller_spec.rb

    require 'rails_helper'

    describe MessagesController, type: :controller do
      let(:user) { create(:user) }
      let(:group) { create(:group) }
      let(:message) { build(:message) }

      describe 'GET #index' do
        context 'when user login' do

          before do
            login_user user
          end

          it "is assigns sg @message" do
            blank_message = Message.new
            get :index, params: { group_id: group }
            expect(assigns(:message).attributes).to eq(blank_message.attributes)
          end

          it "is assigns sg @group" do
            get :index, params: { group_id: group }
            expect(assigns(:group)).to eq(group)
          end

          it "is assigns pl @messages has current_group.users" do
            messages = create_list(:message, 3, user_id: user.id, group_id: group.id)
            get :index, params: { group_id: group }
            expect(assigns(:messages)).to match(messages)
          end

          it "is assings pl @groups has current_user.groups" do
            groups = create_list(:group, 3)
            groups.each do |g|
              g.members.create(user: user)
            end
            get :index, params: { group_id: groups.first.id }
            groups = user.groups
            expect(assigns(:groups)).to eq groups
          end

          it "renders index template?" do
            get :index, params: { group_id: group }
            expect(response).to render_template :index
          end
        end

        context 'when user is not login' do
          it "can redirect to new_user_session?" do
            get :index, params: { group_id: group }
            expect(response).to redirect_to new_user_session_path
          end
        end
      end

      describe 'post #create' do
        context 'when user login and successed processing' do
          before do
            login_user user
          end

          it 'is write in Database?' do
            expect do
              post :create, params: { group_id: group, message: attributes_for(:message) }
            end.to change(Message, :count).by(1)
          end

          it 'is redirect to index template?' do
            post :create, params: { group_id: group, message: attributes_for(:message) }
            expect(response).to redirect_to group_messages_path
          end
        end

        context 'when user login and unsuccessed processing' do
          before do
            login_user user
          end

          it 'is not write in Database?' do
            expect do
              post :create, params: { group_id: group, message: attributes_for(:message, text: nil, image: nil) }
            end.to change(Message, :count).by(0)
          end

          it "is redirect to index template?" do
            message = build(:message, text:"")
            post :create, params: { group_id: group, message: attributes_for(:message) }
            expect(response).to redirect_to group_messages_path
          end
        end

        context 'when user is not login' do
          it "can redirect to new_user_session?" do
            post :create, params: { group_id: group, message: attributes_for(:message) }
            expect(response).to redirect_to new_user_session_path
          end

          it 'can not write in Database?' do
            expect do
              post :create, params: { group_id: group, message: attributes_for(:message) }
            end.to change(Message, :count).by(0)
          end
        end
      end
    end

※改善点などあればぜひご指導ください。
#以上です。
果たしてこんな初心者が書いてよかったのか分かりませんが、自分自身昨日の学習を振り返ることができ、とても勉強になりました。
お読みいただき、ありがとうございました。