コントローラテストの基本を整理。

# はじめに
Ruby on Railsに触れて１ヶ月の初心者です。
自身の学習の振り返りも兼ねて投稿させていただきます。
ところどころ省略してしまっている部分もあるかと思いますが、ご容赦ください。
必要があれば追記します。

この記事では、コントローラテストの基本の流れをトップダウンで書き連ねていきます。

# Railsにおけるテストとは
RSpecを使ってモデルクラスとコントローラクラスが正常に動作するかどうかの確認をすることを、テストと言います。
テストをすることで、思いがけないバグをローンチ前に発見・修正できたり、リファクタリングや機能の追加がしやすくなります。

# 必要なgem

```rb

gem 'devise'

group :development, :test do
 gem "rspec-rails"
 gem "factory_girl_rails", "~> 4.4.1"
 gem 'rails-controller-testing'
end

group :test do
 gem "faker"
end
```
また、database_cleanerやcapybara等といった便利なgemがあります。興味があったら調べてみてください。

# モデル構成

```rb

# message.rb
  belongs_to :group
  belongs_to :user

# group.rb
  has_many :users, through: :members
  has_many :messages
  has_many :members

# user.rb
  has_many :groups, through: :members
  has_many :messages
  has_many :members

# member.rb(中間テーブル)
  belongs_to :group
  belongs_to :user

```

# テストするコントローラ

今回はこちらのcontrollerをテストします。

```rb

class MessagesController < ApplicationController

  before_action :set_group, only: [:index, :create]

  def index
    @message = Message.new
  end

  def create
    @message = Message.new(create_params)
    if @message.save
    redirect_to group_messages_path(@group)
    else
      flash.alert = '内容を入力してください'
      render 'index'
    end
  end

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

  def set_group
    @group = Group.find(params[:group_id])
    @groups = current_user.groups
    @messages = @group.messages.includes(:group)
  end

end
```

テストするのはindexアクションとcreateアクションの２つです。
groupコントローラにmessageコントローラをネストさせているので、パスは/groups/:group_id/messagesになります。

# ファイル構成
rails g rspec:installコマンドで生成されたspecディレクトリ以下にcontrollersディレクトリを作成し、その直下にコントローラのテストを書いていくファイルを作成します。
今回はmessages_controller.rbのテストを行うので、messages_controller_spec.rbという名前で作ることとします。

```
* specディレクトリ

    * controllersディレクトリ

      * messages_conroller_spec.rbファイル

* factoriesディレクトリ

    * messages.rbファイル
    * users.rbファイル
    * groups.rbファイル
    * members.rbファイル
```

それではmessages_controller_spec.rbに基本コードを書いてみます。

## specファイルの基本コード

```rb

require 'rails_helper'

describe MessagesCntroller do

end
```
この基本コードが正常に動くかどうか確認します。

```
$ bundle exec rspec
```

ちなみに、特定のファイルを選択してコマンド実行することもできます。

```
$ bundle exec rspec spec/contorollers/messages_controller_spec.rb
```

実行後、ターミナルに以下のように表示されたらOKです。

```
No examples found.

Finished in 0.06744 seconds (files took 3.46 seconds to load)
0 examples, 0 failures
```

次に、今回のコントローラにおいてどんなテストコードが必要か整理をしてみます。

# テストコードのアウトライン

```rb
describe MessagesControllerについて

  describe 'GETメソッドのindexアクションについて'

    context 'ログインしている場合'

      it '@groupという変数が正しく定義されているか'

      it '@messageという変数が正しく定義されているか'

     it '@groupsという変数が正しく定義されているか'

      it '@messagesという変数が正しく定義されているか'

      it '該当するビューが描画されているか'

    end

    context 'ログインしていない場合'

      it '意図したビューにリダイレクトしているか'

    end

  end

  describe 'POSTメソッドのcreateアクションについて'

    context 'ログインしていて、かつ保存に成功した場合'

      it 'データベースにメッセージの保存ができたか'

      it '意図したビューにリダイレクトしているか'

    end

    context 'ログインしているが、保存に失敗した場合'

      it 'データベースにメッセージの保存が行われなかったか'

      it '意図したビューにリダイレクトしているか'

    end

    context 'ログインしていない場合'

      it '意図したビューにリダイレクトしているか'

    end

  end

end
```

テストコードを書く上でいくつかポイントを挙げます。

 * describeブロックとcontextブロックを用いてexample( it文 )をわかりやすい階層構造に整理する。
 * contextには正常系と異常系の両方を書く。
 * exampleの説明は明示的な能動形の言葉で書く。
 * example一つにつき、一つの結果を期待する。

<details><summary>今回使用するマッチャ</summary>**be_a_new**　...........対象が未保存レコードであるかどうか。
**eq**...........................指定したクラスのインスタンスが期待値と同じかどうか。
**render_template**...引数で指定したアクションがリクエストされた時に自動的に遷移するビューを返すかどうか。
**attributes_for**.........値のハッシュを生成する。
**redirect_to**..............指定したパスにリダイレクトするかどうか。
**change(Controller名 :count) + by(n)**.......レコードがn個増える/減るかどうか</details>

# 展開したテストコード

```rb
require 'rails_helper'

describe MessagesController, type: :controller do
  render_views
  let(:user) { create(:user) }
  let(:group) { create(:group)}
  let(:groups) { user.groups }
  let(:message) { create(:message)}
  let(:messages) { group.messages }
  let(:group_id) do
    {params: { group_id: group.id }}
  end
  let(:params) do
    { params: { group_id: group.id, message: attributes_for(:message) } }
  end

  describe 'GET #index' do
    before do
      login_user user
      get :index, group_id
    end

    context 'login' do

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

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

      it 'assigns the requested messsage to @groups' do
        expect(assigns(:groups)).to eq groups
      end

      it 'assigns the requested messsages to @messages' do
        expect(assigns(:messages)).to eq messages
      end

      it 'renders the :index template' do
        expect(response).to render_template :index
      end
    end

    context 'not login' do

      it 'redirects to new_user_session_path' do
        redirect_to new_user_session_path
      end
    end
  end

  describe 'POST #create' do

    context 'user loged-in and successfully saved' do
      before do
        login_user user
      end

      it 'saves the new message in the database' do
        expect{post :create, params}.to change(Message, :count).by(1)
      end

      it 'redirects to messages#index' do
        post :create, params
        expect(response).to redirect_to group_messages_path
      end
    end

    context 'user loged-in but missed saved' do
      before do
        login_user user
      end

      it 'unsave the new message in the database' do
        expect{
          post :create, params: { group_id: group.id, message: attributes_for(:message, body: nil, image: nil) }
        }.to change(Message, :count).by(0)
end

      it 'redirects to group_messages_path' do
        post :create, params
        expect(response).to redirect_to group_messages_path
      end
    end

    context 'user is not log-in' do

      it 'redirects to sign-in page'do
      post :create, params
        expect(response).to redirect_to new_user_session_path
      end
    end
  end
end
```

ポイント

 * 最初に「require ‘rails_helper’」をつける。
 * FactoryGirlを利用してすっきりまとめる。
 * 適度にDRYをする。（ before~do や letを使用する ）

let~doにしているところは、この形にしないとどうしてもsyntax errorになってしまうため、このスタイルにしています。
未だにerrorになる原因がよくわからないので（ {},()の過不足errorらしい？ ）、わかる方いらっしゃいましたらご教示いただけますと嬉しいです。

# 最後に
自分の学習の振り返りとしてまとめましたが、参考にしていただけたら幸いです。
また、知識の向上のため、ご指摘

# 参考資料

 * 著書「RSpecによるRailsのテスト入門　テスト駆動開発の習得に向けた実践的アプローチ」著：Aaron Summer氏、訳：伊藤淳一氏、秋元利春氏、魚振江氏、2017年5月4日。
 *  [Qiita「使えるRSpec入門・その1『RSpecの基本的な構文や便利な機能を理解する』」](http://qiita.com/jnchito/items/42193d066bd61c740612) 
 *  [Qiita「使えるRSpec入門・その2『使用頻度の高いマッチャを使いこなす』」](http://qiita.com/jnchito/items/2e79a1abe7cd8214caa5)
 *  [Qiita「RSpecによるコントローラのテストで苦労したところ」](http://qiita.com/yatakan/items/2f5a78b14e1b0be010b4)
 *  [Relishapp Matchers](https://relishapp.com/rspec/rspec-rails/v/3-6/docs/matchers)