コントローラーのテスト
コントローラーのテストとは、コントローラー内のメソッドであるアクションが呼ばれた際の挙動をチェックするものになる。
1つのアクションにつき、以下の2点を確かめる。
1.アクション内で定義されているインスタンス変数の値が期待したものになるか
2.アクションの持つビューに正しく遷移するか
つまり、ひとつのアクションに対して2つ以上のexample(it '' do ~ end
)が必要です。基本的には以下のような流れでコードを書く。
1 2 3 4 5 6 7 8 9 10 11 12 |
各アクションはそれぞれリクエストされる際のhttpメソッドが違うが、それぞれ少しずつテストの書き方が異なる。ここでは、Railsに定められている7つのアクションのうちhttpメソッドがget
であるアクションに関してのテストコードの書き方を考える。
事前準備
まずは、Pictweetの元のコードを編集し、正常にテストコードが動作するようにする。
Pictweetの元のコードを編集
Deviseによって定義されるメソッドが利用されている箇所を無効にしておく。
app/controllers/tweets_controller.rbを以下のように編集
各アクションが呼ばれる際にbefore_action
で発動する「ログインしていなかった場合にリダイレクトするメソッド」が呼ばれないよう、before_action
の行をコメントアウトしておく。
1 2 |
#3行目付近の以下のような行をコメントアウト
# before_action :move_to_index, except: [:index]
|
Gemfileに追記
1 2 3 4 5 |
group :development, :test do
~省略~
gem 'rails-controller-testing'
~省略~
end
|
追記して保存したら、bundle install
を実行。
続いて、コントローラーのテスト用Specファイルを作成し、基本となるコードを書き実行できるか確かめる。
Rspecのテストコードは、テストされるコードと同じ階層を作成して、そこに配置する。今回はapp/controllers
以下のファイルに関するテストコードを作成するため、spec
ディレクトリ以下にcontrollersディレクトリを作成する。
Pictweetのディレクトリに、spec/controllers/を作成
上記で作成したspec/controllers
の直下にtweets_controller.rb
のテストコードを書いていくファイルtweets_controller_spec.rb
を作成する。
spec/controllers
以下にtweets_controller_spec.rb
を作成する
ここまでで、以下のようなディレクトリ構成になっていることを確認。
spec/controllersの作成
- pictweet
- app 省略・・・
- spec
- models
- user_spec.rb
- controllers
- tweets_controller_spec.rb
- models
tweets_controller_spec.rb
を以下のように編集
1 2 3 4 5 |
require 'rails_helper'
describe TweetsController do
end
|
モデルのテストコードと同様にrequire 'rails_helper'
を記述した後、describe
を利用し大枠としてクラス名でグループを作成する。
このコードが正常に動作するか確認。RSpecのテストコードを実行するにはbundle exec rspec
コマンドを利用したが、specファイルが複数ある場合は特定のファイルを選択して実行することも可能。今回は、spec/controllers/tweets_controller_spec.rb
を選択して実行してみる。
ターミナルから以下のコマンドを利用し、spec/controllers/tweets_controller_spec.rb
を実行
1 |
$ bundle exec rspec spec/controllers/tweets_controller_spec.rb
|
その後、ターミナルに以下のように表示されればspecファイルは問題なく実行できている。
1 2 3 4 |
No examples found.
Finished in 0.06744 seconds (files took 3.46 seconds to load)
0 examples, 0 failures
|
これで準備は完了。
httpメソッドがgetで呼ばれるアクションのテストコードを書く
7つのアクションの中でも比較的簡単にテストコードを書くことができる「httpメソッドがget
」であるアクションのテストコードを書いていく。
これは「index, new, edit, show」の4種類があるが、Pictweetにおいてはnewアクションが最も単純なアクション。
中身が何もないため、テストすべきことは「newアクションが動いたあとnew.html.erbに遷移するか」のみ。そこで、今回はまずnewアクションのテストコードから記述する。
newアクションのテストコード
newアクション用のテストコードのグループを作成する
1 2 3 4 5 6 7 8 9 |
require 'rails_helper'
describe TweetsController do
describe 'GET #new' do
it "renders the :new template" do
end
end
end
|
コントローラーのアクションもメソッドなので、名前の頭に#
をつけるのは変わらない。違うのは、その前にhttpメソッド名を大文字で書き加えている点である。
また、it と do の間のメッセージは、今回テストしたい内容を書いている。
こちらのグループの中身に、以下のような流れでテストコードを書く。
・まず、擬似的にnewアクションを動かすリクエストを行うコードを書く
・次に、new.html.erbに遷移することを確かめるコードを書く
はじめに、「擬似的なリクエストを行うコード」を書き、擬似的なリクエストを実現するためにはメソッドを利用する。
getメソッド
各httpメソッドには、それぞれ対応するメソッド(get, post, delete, patch)が存在する。引数として、利用したいコントローラーのアクションをシンボル型で渡す。必要なパラメーターが存在する場合は、各パラメーターをハッシュ形式で渡す。
【例】擬似的にshowアクションのリクエストをしたい場合
1 2 3 4 5 6 7 |
describe ◯◯Controller do
describe 'GET #show' do
it "renders the :show template" do
get :show, params: { id: 1 }
end
end
end
|
4行目でget
メソッドを利用している。擬似的にshowアクションを呼び出すリクエストなので、引数はアクション名と渡すパラメーターの2つ。
アクション名は:show
、渡すパラメーターはidというキーに対して適切なオブジェクトである数字の1をバリューにしたハッシュ、id: 1
。
tweets_controller_spec.rbを以下のように編集
1 2 3 4 5 6 7 8 9 10 11 12 |
require 'rails_helper'
describe TweetsController, type: :controller do
describe 'GET #new' do
it "renders the :new template" do
get :new
end
end
end
|
newアクションをリクエストするには引数は必要ないため、記述はget :new
のみ。これで、「擬似的にnewアクションを動かすリクエストを行うコード」を書けた。これはこの後何度も出てくるので、是非書き方を覚えておく。
続いて、new.html.erbに遷移することを確かめるコードを書く。
1 2 3 4 5 6 7 8 9 10 11 |
require 'rails_helper'
describe TweetsController, type: :controller do
describe 'GET #new' do
it "renders the :new template" do
get :new
expect(response).to render_template :new
end
end
end
|
8行目に記述を追加した。expect
メソッドに対してresponse
という引数を渡し、これとrender_template :new
というメソッドの返り値を比較している。
response
example内でリクエストが行われた後の遷移先のビューの情報を持つインスタンス。
render_templateマッチャ
引数にシンボル型でアクション名を取る。引数で指定したアクションがリクエストされた時に自動的に遷移するビューを返す。この部分は、他のアクションのテストに関してもrender_template
マッチャに対して適切なアクション名を渡すだけで大丈夫。
これで「newアクションが動いたあとnew.html.erbに遷移するか」のテストは完成。つまり、newアクションに関するテストコードは完成。試しに、一度テストを実行。
spec/controllers/tweets_controller_spec.rb
を実行
1 |
$ bundle exec rspec spec/controllers/tweets_controller_spec.rb
|
以下のように表示されれば、テストは無事にパスしている。
1 2 3 4 5 6 |
TweetsController
GET #new
renders the :new template
Finished in 0.13692 seconds (files took 3.21 seconds to load)
1 example, 0 failures
|
editアクションのテストコード
続いて、editアクションのテストコードを書く。
先ほどのnewアクションでは、「アクションの持つビューに正しく遷移するか」のみをテストした。
しかし、tweets_controller.rb
のeditアクションを確認すると、@tweet
というインスタンス変数を定義していることがわかる。
なので、今度はコントローラーのテストのもう一つの基本である「アクション内で定義されているインスタンス変数の値が期待したものになるか」についてのexampleも必要。
まずは、exampleを書く。
newアクションのテストコードに続ける形で、下記を追記。
1 2 3 4 5 6 7 8 9 |
あらかじめ、2つのexampleを作成。まずは、上の方の「インスタンス変数の値を確かめる」テストコードを書く。
さて、editアクションが何をしているのかを考えると、まずはtweetsテーブルにレコードが入っていなければならない。そこで、このexampleの中でtweetsテーブルにレコードを保存する。そのためには、factory_botを利用する。
spec/factories/tweets.rbを作成し、中身を以下のように編集
1 2 3 4 5 6 7 |
これで、factory_botを利用してtweetを作成できるようになった。tweetをデータベースに保存したいので、create
メソッドを利用すれば良い。
1 2 3 4 5 6 7 8 9 10 |
4行目の記述を追加。
続いて、擬似的なリクエストする。今回もhttpメソッドはgetなので、get
メソッドを利用する。
1 2 3 4 5 6 7 8 9 10 11 |
5行目の記述を追加した。params: { id: tweet }
とすることで、idというキーのバリューに先ほど作成したインスタンスのidをセットすることができる。
取得するレコードの情報は、まさに一行上で作成しているTweetクラスのインスタンス「tweet」なので、id: tweet
としている。
続いて、editアクションで取得しているインスタンス変数が、上記で作成した変数tweetと一致するかを確かめるためのエクスペクテーションを書く。そのために、expect
メソッドの引数にassigns
というメソッドを利用する。
assignsメソッド
コントローラーのテスト時、アクションで定義しているインスタンス変数をテストするためのメソッド。
引数に、直前でリクエストしたアクション内で定義されているインスタンス変数をシンボル型で取る。
通常はexpect
メソッドの引数としてよく利用する。
今回editアクションで定義しているインスタンス変数の名前は@tweet
なので、assigns
メソッドの引数は:tweet
となる。
1 2 3 4 5 6 7 8 9 10 11 12 |
6行目を追記した。こうすることで、リクエストされたeditアクションの中で定義されている@tweet
が、間違いなくこちらで作成しているtweet
となっているかが確かめられる。
これで、「アクション内で定義されているインスタンス変数の値が期待したものになるか」のテストコードを書くことができた。
もうひとつのexampleである「期待したビューに遷移するか」のテストコードを書いてみる。
「editアクションをリクエストした後、edit.html.erbに遷移するか」を確かめるテストコードを書く。以下を追記できればeditアクションのテストは完了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#省略
describe 'GET #edit' do
it "assigns the requested tweet to @tweet" do
tweet = create(:tweet)
get :edit, params: { id: tweet }
expect(assigns(:tweet)).to eq tweet
end
it "renders the :edit template" do
tweet = create(:tweet)
get :edit, params: { id: tweet }
expect(response).to render_template :edit
end
end
#省略
|
indexアクションのテストコード
続いて、indexアクションのテストコードを書く。
indexアクションに関してのポイントは、indexアクションで定義している@tweetsは配列の形で取得されてくるということ。
まずは空のexampleを作成する。
editアクションのテストコードに続ける形で、下記を追記。
1 2 3 4 5 6 7 8 9 |
#省略
describe 'GET #index' do
it "populates an array of tweets ordered by created_at DESC" do
end
it "renders the :index template" do
end
end
#省略
|
上の方のexampleが、インスタンス変数の値を確かめるためのもの。tweetsコントローラーのindexアクションでは、まずテーブルから全てのレコードを取得してきているため、テストの際も複数のレコードが存在しなければならない。
1 2 3 4 5 |
class TweetsController < ApplicationController
def index
@tweets = Tweet.includes(:user).page(params[:page]).per(5).order("created_at DESC")
end
|
そこで、indexアクションのexampleではまずはじめに、tweetsテーブルに複数のレコードを作成することからはじめる。そのためには、factory_botのcreate_listメソッドを利用する。
create_list
factory_botの設定ファイルに存在しているリソースを複数作成したい場合に以下のように利用できる。
1 |
hoges = create_list(:hoge, 3)
|
第一引数に作成したいリソースをシンボル型で、第二引数に作成したい個数を数字で渡す。
上記の式では、hogeというリソースを3つ作成しレコードに保存している。
これを利用して、tweetのリソースを3つ、作成する。コントローラーのspecファイルにてcreate_list
メソッドを利用する。
1 2 3 4 5 6 7 8 9 10 11 |
#省略
describe 'GET #index' do
it "populates an array of tweets ordered by created_at DESC" do
tweets = create_list(:tweet, 3)
get :index
end
it "renders the :index template" do
end
end
#省略
|
4, 5行目を書き加えた。tweetのレコードを3つ保存した上で、そのインスタンスをtweets
という変数に代入している。
5行目では、indexアクションへの擬似的なリクエストを行っている。
続いてuserのfactoryの内容を変更する。
1 2 3 4 5 6 7 8 |
FactoryBot.define do
factory :user do
nickname {"abe"}
password {"00000000"}
password_confirmation {"00000000"}
sequence(:email) {Faker::Internet.email}
end
end
|
tweetを3つ作ると、それに伴ってuserも3名分作成される。その時に、同じemailを使用するとバリデーションに引っかかってエラーになる。そのためFakerを使って異なるものになるように実装する。
Faker
emailや電話番号、名前などのダミーデータを作成するためのGem。インストール後、factory_botの設定ファイルの中でFakerのメソッドを利用し、ダミーデータを生成する。
Fakerをインストール
Gemfileの下部に、以下の記述を追加。これは、test環境でのみ読み込まれるという記述方法。
1 2 3 |
group :test do
gem 'faker', "~> 2.8"
end
|
その後、忘れずにbundle install
コマンドを実行。
【例】Fakerダミーデータの生成方法
1 2 |
{ Faker::Internet.email }
=> "rodrick.wyman@rosenbaum.org"
|
続いてindexアクションで定義されているインスタンス変数@tweets
の値を確かめるエクスペクテーションを記述する。期待される値が配列の場合は、matchというマッチャを利用。
matchマッチャ
引数に配列クラスのインスタンスをとり、expectの引数と比較するマッチャ。配列の中身の順番までチェックする。
match
マッチャを利用してエクスペクテーションを書く。
1 2 3 4 5 6 7 8 9 10 11 |
describe 'GET #index' do
it "populates an array of tweets ordered by created_at DESC" do
tweets = create_list(:tweet, 3)
get :index
expect(assigns(:tweets)).to match(tweets)
end
it "renders the :index template" do
end
end
#省略
|
この状態でテストを実行すると、テストはパスする。しかし、このままでは厳密にテストを作成することはできていない。tweets_controller.rbを見ると、indexアクションで定義されているインスタンス変数@tweets
は、created_atで降順にソートしているからである。この状態を再現しなければならない。
現状、create_list
メソッドで生成しているtweetのレコードのcreated_atの値は全て同じになってしまっている。これをランダムな値にするために先ほどのFakerを利用する。
1 2 3 4 5 6 7 8 |
これで、tweetのリソースひとつひとつのcreated_atはランダムに生成される。続いて、example内で定義しているtweetsの中身の順番を、created_atを基準に降順で並び替える。そのためには、sort
メソッドを利用する。
1 2 3 4 5 6 7 8 9 10 11 |
describe 'GET #index' do
it "populates an array of tweets ordered by created_at DESC" do
tweets = create_list(:tweet, 3)
get :index
expect(assigns(:tweets)).to match(tweets.sort{ |a, b| b.created_at <=> a.created_at } )
end
it "renders the :index template" do
end
end
#省略
|
5行目で、sort
メソッドを利用している。
これで、indexアクションに関するテストコードは半分完成。
あとは、ページ遷移のexampleである。
1 2 3 4 5 6 7 8 9 10 11 12 |
describe 'GET #index' do
it "populates an array of tweets ordered by created_at DESC" do
tweets = create_list(:tweet, 3)
get :index
expect(assigns(:tweets)).to match(tweets.sort{|a, b| b.created_at <=> a.created_at })
end
it "renders the :index template" do
get :index
expect(response).to render_template :index
end
end
|