LoginSignup
47
50

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-07-16

はじめに

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

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

Railsにおけるテストとは

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

必要なgem


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があります。興味があったら調べてみてください。

モデル構成


# 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をテストします。


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アクションの2つです。
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ファイルの基本コード


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

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

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

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一つにつき、一つの結果を期待する。

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

展開したテストコード

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らしい? )、わかる方いらっしゃいましたらご教示いただけますと嬉しいです。

最後に

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

参考資料

47
50
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
47
50