Rspec でコントローラーのテスト その1 Rspec Tips, Rails

modified at:08 March 2016

Rspecでビヘイビア(振舞)駆動開発をしよう。でもテストの仕方がわからないとできませんね。今回は、コントローラーのテストに挑戦です。

「Rspecのインストールとテストの基本」「Rspecでモデルのテスト」が前提条件になりますので、ご自分で試してみたい方はインストールその他をしておいてください。

今回は以下のことを行います。

  1. UsersController の仕様を決める
  2. FactoryGirlの attribute_for メソッドを使う
  3. let を使い異なるテストで同じ値が使えるようにする
  4. assigns メソッドでコントローラー内のインスタンス変数に代入されたオブジェクトを取得する
  5. マッチャー changebe_persistedredirect_torender_template を使う
    1. change : 値が何から変化したか、何に変化したか、いくつ増えたか減ったかを検証できる
    2. be_persisted : インスタンスがデータベースに登録されたものかを検証できる
    3. redirect_to : どこへリダイレクトされたかを検証できる
    4. render_template : どのテンプレート(*.html.erb)が描画されたかを検証できる

Usersコントローラーのテスト

テスト作成の手順を確認しておきましょう。

  1. テストを行うクラスやメソッドの仕様を決める。
  2. テストファーストで仕様を実装していく。 つまり、テストを先に作り、テストが成功するよう作業を行う。

Usersコントローラーの仕様

spec/controllers/users_controller_spec.rb を編集していきます。仕様を考えてみましょう。

まずコントローラーのメソッドを整理して一つ一つの仕様を見ていくことにします。

File: spec/controllers/users_controller_spec.rb
 1 require 'rails_helper'
 2 
 3 RSpec.describe UsersController, type: :controller do
 4 
 5   describe "GET #index"
 6   describe "GET #show"
 7   describe "GET #new"
 8   describe "GET #edit"
 9   describe "POST #create"
10   describe "PUT #update"
11   describe "DELETE #destroy"
12 
13 end
14      
  • index の仕様
    • @users にすべてのユーザーを割り当てる。
  • show の仕様
    • @user にリクエストされたユーザーを割り当てる。
  • new の仕様
    • @user に新規ユーザーを割り当てる。
  • edit の仕様
    • @user にリクエストされたユーザーを割り当てる。
  • create の仕様
    • 正常な値でユーザーを作ることができ、その新規ユーザーを @user に割り当て、@user にリダイレクトされる。
    • 正常な値でないとユーザーを登録することができず、newへ戻される。
  • update の仕様
    • 正常な値でリクエストされたユーザーをアップデートでき、そのユーザーを @user に割り当て、@user にリダイレクトされる。
    • 正常な値でないとユーザーをアップデートできず、editへ戻される。
  • destroy の仕様
    • リクエストされたユーザーを削除し、ユーザー一覧へリダイレクトされる。

users_controller_spec.rbTopへ

index、show、edit などのテストでは毎回ユーザーを作る必要があるのでFactoryGirlを使ってユーザーの属性を受け取ることにします。

index、show、new、edit のテスト

attributes_for、let

定義したファクトリーから属性だけを受け取るメソッド attributes_for を使い、その値を各テストで使えるように let で定義します。

      let(:valid_attributes) {
  FactoryGirl.attributes_for(:user, role: FactoryGirl.create(:role_user))
}
    
   

この let によってファクトリー:userで定義された値が、 :valid_attributes で参照できます。

assigns

コントローラーのインスタンス変数に代入されたオブジェクトは、 assigns メソッドで参照できます。参照する場合には、インスタンス変数名から “@” を除いたものをシンボルまたは文字列にしたものをキーとして指定します。またassigns の呼び出しは、assigns(:user) という形式でも呼び出せます。

File: spec/controllers/users_controller_spec.rb#index
 1 require 'rails_helper'
 2 
 3 RSpec.describe UsersController, type: :controller do
 4 
 5   let(:valid_attributes) {
 6     FactoryGirl.attributes_for(:user, role: FactoryGirl.create(:role_user))
 7   }
 8 
 9   describe "GET #index" do
10     it "@users にすべてのユーザーを割り当てる。" do
11       user = User.create! valid_attributes
12       get :index
13       expect(assigns(:users)).to eq([user])
14     end
15   end
16 
17   describe "GET #show"
18   describe "GET #new"
19   describe "GET #edit"
20   describe "POST #create"
21   describe "PUT #update"
22   describe "DELETE #destroy"
23 
24 end

index、show、new、edit のテストはほぼ同じなので一気に作ることにします。

get(やpost)で値を送りたい時には、get :show に続けて get :show, {:id => user.id} のようにブロックで渡します。

File: spec/controllers/users_controller_spec.rb
 1   describe "GET #index" do
 2     it "@users にすべてのユーザーを割り当てる。" do
 3       user = User.create! valid_attributes
 4       get :index
 5       expect(assigns(:users)).to eq([user])
 6     end
 7   end
 8 
 9   describe "GET #show" do
10     it "@user にリクエストされたユーザーを割り当てる。" do
11       user = User.create! valid_attributes
12       get :show, {:id => user.to_param}
13       expect(assigns(:user)).to eq(user)
14     end
15   end
16 
17   describe "GET #new" do
18     it "@user に新規ユーザーを割り当てる。" do
19       get :new
20       expect(assigns(:user)).to be_a_new(User)
21     end
22   end
23 
24   describe "GET #edit" do
25     it "@user にリクエストされたユーザーを割り当てる。" do
26       user = User.create! valid_attributes
27       get :edit, {:id => user.to_param}
28       expect(assigns(:user)).to eq(user)
29     end
30   end

マッチャー be_a_new(User) は、指定したクラスのインスタンスで、まだ登録されていない(not_to be_persisted)時に通るマッチャーです。

テストがすべて通りグリーンなことを確かめます。

create メソッドのテスト

context “場合分け” do

createの仕様のところで、ユーザーを作る時に「正常な値」の場合と「正常な値でない」場合とに分けて書きました。このように、条件によってテストを分けるようなときには context を使って次のようにテストを書いていきます。context とは「前後関係、文脈、周囲の情況、背景 」といった意味なので、場合を分けてテストをするときに使います。

File: spec/controllers/users_controller_spec.rb
1 describe "POST #create" do
2   context "正常な値の時" do
3     it "登録できる。"
4   end
5 
6   context "正常な値ではない時" do
7     it "登録できない。"
8   end
9 end

また、context の下に describe を入れ子にして記述することもできます。

仕様を整理してテスト項目に分割してみます。

File: spec/controllers/users_controller_spec.rb
 1 describe "POST #create" do
 2   context "正常な値の時" do
 3     it "新規ユーザーを登録できる。"
 4     it "@userに新規ユーザーが割り当てられる。"
 5     it "登録されたユーザーの詳細画面へリダイレクトされる。"
 6   end
 7 
 8   context "正常な値ではない時" do
 9     it "@userに新規ユーザーが割り当てられるが、登録されない。"
10     it "new画面へ戻される。"
11   end
12 end

マッチャー change

何かを行った結果、状態が変化したことを検証するマッチャーです。

使い方

  • expect { do_something }.to change(object, :attribute)
  • expect { do_something }.to change { object.attribute }
    • change(object, :count).from(0).to(1) countが「0」から「1」に変わった。
    • change(object, :count).by(1) countが「1」増えた。
    • change(object, :count).by(-1) countが「1」減った。

expect の引数がブロックになっていることに注目してください。changeは、コードブロックを引き受け、状態が変化したことを特定します。

このマッチャーを使って新規ユーザーが登録されるテストを作ります。1件のデータが登録できたら User.all のようなSQLの結果でカウントが「1」だけ増えることを使います。

File: spec/controllers/users_controller_spec.rb#create
1 describe "POST #create" do
2   context "正常な値の時" do
3     it "新規ユーザーを登録できる。" do
4       expect {
5         post :create, {:user => valid_attributes}
6       }.to change(User, :count).by(1)
7     end
8 end

これをテストすると失敗します。

      [rails_app]$ rspec spec/controllers/users_controller_spec.rb
....F

Failures:

  1) UsersController POST #create 正常な値の時 新規ユーザーを登録できる。
     Failure/Error: expect {
     expected #count to have changed by 1, but was changed by 0
     # ./spec/controllers/users_controller_spec.rb:47:in `block (4 levels) in <top (required)>'

Finished in 0.12517 seconds (files took 1.53 seconds to load)
5 examples, 1 failure

Failed examples:

rspec ./spec/controllers/users_controller_spec.rb:46 # UsersController POST #create 正常な値の時 新規ユーザーを登録できる。
   

失敗の原因は、コントローラーが受け取ったパラメーター(下記app/controllers/users_controller.rbの2行目のuser_params)の中に権限roleがないことです。createメソッドの中でrole属性を設定しなければなりません。

role属性は、letでファクトリー:userを作る時に入れたはずなのに?と思ってしまいました。コントローラーで値を受け取れていないときには「ストロングパラメーター」をチェックしましょう。

ストロングパラメーター(UsersControllerのuser_paramsメソッドで定義されたパラメーター)に「role」が含まれていないために、属性を受け取る前に、role属性は取り除かれてしまうのです(ストロングパラメーターについては別のところで紹介しますので今はストロングパラメーターにない(role_idはあってもroleはない)属性はコントローラーでは受け取れないと思ってください)。

テストが成功するようにコントロ-ラーを編集します。

File: app/controllers/users_controller.rb
 1 def create
 2   @user = User.new(user_params)
 3   @user.role = Role.where("role_name = ?", "general_user").first
 4 
 5   respond_to do |format|
 6     if @user.save
 7       format.html { redirect_to @user, notice: 'User was successfully created.' }
 8       format.json { render :show, status: :created, location: @user }
 9     else
10       format.html { render :new }
11       format.json { render json: @user.errors, status: :unprocessable_entity }
12     end
13   end
14 end

3行目を追加しました。これでテストは通るはずです。

      [rails_app]$ rspec spec/controllers/users_controller_spec.rb
.....

Finished in 0.12629 seconds (files took 1.61 seconds to load)
5 examples, 0 failures
   

では、テストの作成を続けましょう。

create のテスト

  1. createメソッドを使い登録ができたかどうかをテストする
    1. 登録件数が「1」増えたことを検証する方法 マッチャーに change を使う。

    2. UsersController内のインスタンス@userが実際に登録されたことを検証する方法 対象 assigns(:user) にマッチャー be_persisted を適用することで登録されたことが検証できる。

  2. リダイレクト先をテストする
    • 対象 response にマッチャー redirect_to(User.last) を適用させる。
  3. レンダー先をテストする
    • 対象 response にマッチャー render_template("new") を適用させる。
  4. 正常ではない値を用意する

以上からcreateのテストは次のようになります。

File: spec/controllers/users_controller_spec.rb
 1 RSpec.describe UsersController, type: :controller do
 2 
 3   let(:valid_attributes) {
 4     FactoryGirl.attributes_for(:user, role: FactoryGirl.create(:role_user))
 5   }
 6 
 7   let(:invalid_attributes) {
 8     FactoryGirl.attributes_for(:user, name: nil, password: "aa", email: "aaa")
 9   }
10            途中省略
11 
12   describe "POST #create" do
13     context "正常な値の時" do
14       it "新規ユーザーを登録できる。" do
15         expect {
16           post :create, {:user => valid_attributes}
17         }.to change(User, :count).by(1)
18       end
19 
20       it "@userに新規ユーザーが割り当てられる。" do
21         post :create, {:user => valid_attributes}
22         expect(assigns(:user)).to be_a(User)
23         expect(assigns(:user)).to be_persisted
24       end
25 
26       it "登録されたユーザーの詳細画面へリダイレクトされる。" do
27         post :create, {:user => valid_attributes}
28         expect(response).to redirect_to(User.last)
29       end
30     end
31 
32     context "正常な値ではない時" do
33       it "@userに新規ユーザーが割り当てられるが、登録されない。" do
34         post :create, {:user => invalid_attributes}
35         expect(assigns(:user)).to be_a_new(User)
36         expect(assigns(:user)).not_to be_persisted
37       end
38 
39       it "new画面へ戻される。" do
40         post :create, {:user => invalid_attributes}
41         expect(response).to render_template("new")
42       end
43     end
44   end
45            途中省略
46 
47 end

update メソッドのテスト

createの時と同様に「正常な値」の場合と「正常な値ではない」場合に分けて整理すると、次にようになります。

File: spec/controllers/users_controller_spec.rb#update
 1 describe "PUT #update" do
 2   context "正常な値の時" do
 3     it "リクエストされたユーザーを更新できる。"
 4     it "@userにリクエストされたユーザーが割り当てられる。"
 5     it "ユーザーの詳細画面へリダイレクトされる。"
 6   end
 7 
 8   context "正常な値ではない時" do
 9     it "@userにリクエストされたユーザーが割り当てられるが、登録されない。"
10     it "edit画面へ戻される。"
11   end
12 end

アップデートする属性を let で定義します。それを使ってアップデートする以外はcreateの時と同じようにできます。

File: spec/controllers/users_controller_spec.rb#update
 1 describe "PUT #update" do
 2   context "正常な値の時" do
 3     let(:new_attributes) {
 4       FactoryGirl.attributes_for(:user, name: "new_user", password: "new_user_PASSWORD", email: "new_user@example.com")
 5     }
 6 
 7     it "リクエストされたユーザーを更新できる。" do
 8       user = User.create! valid_attributes
 9       put :update, {:id => user.to_param, :user => new_attributes}
10       user.reload
11 
12       expect(user.name).to eq("new_user")
13       expect(user.password).to eq("new_user_PASSWORD")
14       expect(user.email).to eq("new_user@example.com")
15     end
16 
17     it "@userにリクエストされたユーザーが割り当てられる。" do
18       user = User.create! valid_attributes
19       put :update, {:id => user.to_param, :user => valid_attributes}
20       expect(assigns(:user)).to eq(user)
21     end
22 
23     it "ユーザーの詳細画面へリダイレクトされる。" do
24       user = User.create! valid_attributes
25       put :update, {:id => user.to_param, :user => valid_attributes}
26       expect(response).to redirect_to(user)
27     end
28   end
29 
30   context "正常な値ではない時" do
31     it "@userにリクエストされたユーザーが割り当てられるが、登録されない。" do
32       user = User.create! valid_attributes
33       put :update, {:id => user.to_param, :user => invalid_attributes}
34       expect(assigns(:user)).to eq(user)
35     end
36     it "edit画面へ戻される。" do
37       user = User.create! valid_attributes
38       put :update, {:id => user.to_param, :user => invalid_attributes}
39       expect(response).to render_template("edit")
40     end
41   end
42 end

destroy メソッドのテスト

マッチャーchangeメソッドを使ってデータが1件減ったこと、ユーザー一覧へリダイレクトされることをテストします。

File: spec/controllers/users_controller_spec.rb#destroy
 1 describe "DELETE #destroy" do
 2   it "リクエストされたユーザーを削除する。" do
 3     user = User.create! valid_attributes
 4     expect {
 5       delete :destroy, {:id => user.to_param}
 6     }.to change(User, :count).by(-1)
 7   end
 8 
 9   it "ユーザー一覧へリダイレクトする。" do
10     user = User.create! valid_attributes
11     delete :destroy, {:id => user.to_param}
12     expect(response).to redirect_to(users_url)
13   end
14 end

以上で、scaffoldで作られたspecファイルとほぼ同じものを作ることができました。

次回は、ログイン機能を追加して、そのテストに挑戦です。

  created at:08 June 2015




[投稿する]場合は、 枠の中の図形をマウスでドラッグして、(あるいは指で)なぞって下さい。それほど厳密でなくても大丈夫です。