はじめに
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らしい? )、わかる方いらっしゃいましたらご教示いただけますと嬉しいです。
最後に
自分の学習の振り返りとしてまとめましたが、参考にしていただけたら幸いです。
また、知識の向上のため、ご指摘
参考資料
- 著書「RSpecによるRailsのテスト入門 テスト駆動開発の習得に向けた実践的アプローチ」著:Aaron Summer氏、訳:伊藤淳一氏、秋元利春氏、魚振江氏、2017年5月4日。
- Qiita「使えるRSpec入門・その1『RSpecの基本的な構文や便利な機能を理解する』」
- Qiita「使えるRSpec入門・その2『使用頻度の高いマッチャを使いこなす』」
- Qiita「RSpecによるコントローラのテストで苦労したところ」
- Relishapp Matchers