Rspecでビヘイビア(振舞)駆動開発をしよう。でもテストの仕方がわからないとできませんね。今回は、コントローラーのテストに挑戦です。
「Rspecのインストールとテストの基本」と「Rspecでモデルのテスト」が前提条件になりますので、ご自分で試してみたい方はインストールその他をしておいてください。
今回は以下のことを行います。
- UsersController の仕様を決める
- FactoryGirlの
attribute_for
メソッドを使う let
を使い異なるテストで同じ値が使えるようにするassigns
メソッドでコントローラー内のインスタンス変数に代入されたオブジェクトを取得する- マッチャー
change
、be_persisted
、redirect_to
、render_template
を使う- change : 値が何から変化したか、何に変化したか、いくつ増えたか減ったかを検証できる
- be_persisted : インスタンスがデータベースに登録されたものかを検証できる
- redirect_to : どこへリダイレクトされたかを検証できる
- render_template : どのテンプレート(*.html.erb)が描画されたかを検証できる
Usersコントローラーのテスト
テスト作成の手順を確認しておきましょう。
- テストを行うクラスやメソッドの仕様を決める。
- テストファーストで仕様を実装していく。 つまり、テストを先に作り、テストが成功するよう作業を行う。
Usersコントローラーの仕様
spec/controllers/users_controller_spec.rb を編集していきます。仕様を考えてみましょう。
まずコントローラーのメソッドを整理して一つ一つの仕様を見ていくことにします。
File: spec/controllers/users_controller_spec.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
require 'rails_helper'
RSpec.describe UsersController, type: :controller do
describe "GET #index"
describe "GET #show"
describe "GET #new"
describe "GET #edit"
describe "POST #create"
describe "PUT #update"
describe "DELETE #destroy"
end
- index の仕様
- @users にすべてのユーザーを割り当てる。
- show の仕様
- @user にリクエストされたユーザーを割り当てる。
- new の仕様
- @user に新規ユーザーを割り当てる。
- edit の仕様
- @user にリクエストされたユーザーを割り当てる。
- create の仕様
- 正常な値でユーザーを作ることができ、その新規ユーザーを @user に割り当て、@user にリダイレクトされる。
- 正常な値でないとユーザーを登録することができず、newへ戻される。
- update の仕様
- 正常な値でリクエストされたユーザーをアップデートでき、そのユーザーを @user に割り当て、@user にリダイレクトされる。
- 正常な値でないとユーザーをアップデートできず、editへ戻される。
- destroy の仕様
- リクエストされたユーザーを削除し、ユーザー一覧へリダイレクトされる。
users_controller_spec.rb
index、show、edit などのテストでは毎回ユーザーを作る必要があるのでFactoryGirlを使ってユーザーの属性を受け取ることにします。
index、show、new、edit のテスト
attributes_for、let
定義したファクトリーから属性だけを受け取るメソッド attributes_for
を使い、その値を各テストで使えるように let
で定義します。
1
2
3
let(:valid_attributes) {
FactoryGirl.attributes_for(:user, role: FactoryGirl.create(:role_user))
}
この let
によってファクトリー:userで定義された値が、 :valid_attributes
で参照できます。
assigns
コントローラーのインスタンス変数に代入されたオブジェクトは、 assigns
メソッドで参照できます。参照する場合には、インスタンス変数名から “@” を除いたものをシンボルまたは文字列にしたものをキーとして指定します。またassigns の呼び出しは、assigns(:user) という形式でも呼び出せます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
require 'rails_helper'
RSpec.describe UsersController, type: :controller do
let(:valid_attributes) {
FactoryGirl.attributes_for(:user, role: FactoryGirl.create(:role_user))
}
describe "GET #index" do
it "@users にすべてのユーザーを割り当てる。" do
user = User.create! valid_attributes
get :index
expect(assigns(:users)).to eq([user])
end
end
describe "GET #show"
describe "GET #new"
describe "GET #edit"
describe "POST #create"
describe "PUT #update"
describe "DELETE #destroy"
end
index、show、new、edit のテストはほぼ同じなので一気に作ることにします。
get(やpost)で値を送りたい時には、get :show に続けて get :show, {:id => user.id}
のようにブロックで渡します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
describe "GET #index" do
it "@users にすべてのユーザーを割り当てる。" do
user = User.create! valid_attributes
get :index
expect(assigns(:users)).to eq([user])
end
end
describe "GET #show" do
it "@user にリクエストされたユーザーを割り当てる。" do
user = User.create! valid_attributes
get :show, {:id => user.to_param}
expect(assigns(:user)).to eq(user)
end
end
describe "GET #new" do
it "@user に新規ユーザーを割り当てる。" do
get :new
expect(assigns(:user)).to be_a_new(User)
end
end
describe "GET #edit" do
it "@user にリクエストされたユーザーを割り当てる。" do
user = User.create! valid_attributes
get :edit, {:id => user.to_param}
expect(assigns(:user)).to eq(user)
end
end
マッチャー be_a_new(User)
は、指定したクラスのインスタンスで、まだ登録されていない(not_to be_persisted)時に通るマッチャーです。
テストがすべて通りグリーンなことを確かめます。
create メソッドのテスト
context “場合分け” do
createの仕様のところで、ユーザーを作る時に「正常な値」の場合と「正常な値でない」場合とに分けて書きました。このように、条件によってテストを分けるようなときには context
を使って次のようにテストを書いていきます。context とは「前後関係、文脈、周囲の情況、背景 」といった意味なので、場合を分けてテストをするときに使います。
1
2
3
4
5
6
7
8
9
describe "POST #create" do
context "正常な値の時" do
it "登録できる。"
end
context "正常な値ではない時" do
it "登録できない。"
end
end
また、context の下に describe を入れ子にして記述することもできます。
仕様を整理してテスト項目に分割してみます。
1
2
3
4
5
6
7
8
9
10
11
12
describe "POST #create" do
context "正常な値の時" do
it "新規ユーザーを登録できる。"
it "@userに新規ユーザーが割り当てられる。"
it "登録されたユーザーの詳細画面へリダイレクトされる。"
end
context "正常な値ではない時" do
it "@userに新規ユーザーが割り当てられるが、登録されない。"
it "new画面へ戻される。"
end
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」だけ増えることを使います。
1
2
3
4
5
6
7
8
describe "POST #create" do
context "正常な値の時" do
it "新規ユーザーを登録できる。" do
expect {
post :create, {:user => valid_attributes}
}.to change(User, :count).by(1)
end
end
これをテストすると失敗します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[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はない)属性はコントローラーでは受け取れないと思ってください)。
テストが成功するようにコントロ-ラーを編集します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def create
@user = User.new(user_params)
@user.role = Role.where("role_name = ?", "general_user").first
respond_to do |format|
if @user.save
format.html { redirect_to @user, notice: 'User was successfully created.' }
format.json { render :show, status: :created, location: @user }
else
format.html { render :new }
format.json { render json: @user.errors, status: :unprocessable_entity }
end
end
end
3行目を追加しました。これでテストは通るはずです。
1
2
3
4
5
6
[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 のテスト
- createメソッドを使い登録ができたかどうかをテストする
登録件数が「1」増えたことを検証する方法 マッチャーに
change
を使う。UsersController内のインスタンス@userが実際に登録されたことを検証する方法 対象
assigns(:user)
にマッチャーbe_persisted
を適用することで登録されたことが検証できる。
- リダイレクト先をテストする
- 対象
response
にマッチャーredirect_to(User.last)
を適用させる。
- 対象
- レンダー先をテストする
- 対象
response
にマッチャーrender_template("new")
を適用させる。
- 対象
- 正常ではない値を用意する
以上からcreateのテストは次のようになります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
RSpec.describe UsersController, type: :controller do
let(:valid_attributes) {
FactoryGirl.attributes_for(:user, role: FactoryGirl.create(:role_user))
}
let(:invalid_attributes) {
FactoryGirl.attributes_for(:user, name: nil, password: "aa", email: "aaa")
}
途中省略
describe "POST #create" do
context "正常な値の時" do
it "新規ユーザーを登録できる。" do
expect {
post :create, {:user => valid_attributes}
}.to change(User, :count).by(1)
end
it "@userに新規ユーザーが割り当てられる。" do
post :create, {:user => valid_attributes}
expect(assigns(:user)).to be_a(User)
expect(assigns(:user)).to be_persisted
end
it "登録されたユーザーの詳細画面へリダイレクトされる。" do
post :create, {:user => valid_attributes}
expect(response).to redirect_to(User.last)
end
end
context "正常な値ではない時" do
it "@userに新規ユーザーが割り当てられるが、登録されない。" do
post :create, {:user => invalid_attributes}
expect(assigns(:user)).to be_a_new(User)
expect(assigns(:user)).not_to be_persisted
end
it "new画面へ戻される。" do
post :create, {:user => invalid_attributes}
expect(response).to render_template("new")
end
end
end
途中省略
end
update メソッドのテスト
createの時と同様に「正常な値」の場合と「正常な値ではない」場合に分けて整理すると、次にようになります。
1
2
3
4
5
6
7
8
9
10
11
12
describe "PUT #update" do
context "正常な値の時" do
it "リクエストされたユーザーを更新できる。"
it "@userにリクエストされたユーザーが割り当てられる。"
it "ユーザーの詳細画面へリダイレクトされる。"
end
context "正常な値ではない時" do
it "@userにリクエストされたユーザーが割り当てられるが、登録されない。"
it "edit画面へ戻される。"
end
end
アップデートする属性を let
で定義します。それを使ってアップデートする以外はcreateの時と同じようにできます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
describe "PUT #update" do
context "正常な値の時" do
let(:new_attributes) {
FactoryGirl.attributes_for(:user, name: "new_user", password: "new_user_PASSWORD", email: "new_user@example.com")
}
it "リクエストされたユーザーを更新できる。" do
user = User.create! valid_attributes
put :update, {:id => user.to_param, :user => new_attributes}
user.reload
expect(user.name).to eq("new_user")
expect(user.password).to eq("new_user_PASSWORD")
expect(user.email).to eq("new_user@example.com")
end
it "@userにリクエストされたユーザーが割り当てられる。" do
user = User.create! valid_attributes
put :update, {:id => user.to_param, :user => valid_attributes}
expect(assigns(:user)).to eq(user)
end
it "ユーザーの詳細画面へリダイレクトされる。" do
user = User.create! valid_attributes
put :update, {:id => user.to_param, :user => valid_attributes}
expect(response).to redirect_to(user)
end
end
context "正常な値ではない時" do
it "@userにリクエストされたユーザーが割り当てられるが、登録されない。" do
user = User.create! valid_attributes
put :update, {:id => user.to_param, :user => invalid_attributes}
expect(assigns(:user)).to eq(user)
end
it "edit画面へ戻される。" do
user = User.create! valid_attributes
put :update, {:id => user.to_param, :user => invalid_attributes}
expect(response).to render_template("edit")
end
end
end
destroy メソッドのテスト
マッチャーchangeメソッドを使ってデータが1件減ったこと、ユーザー一覧へリダイレクトされることをテストします。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
describe "DELETE #destroy" do
it "リクエストされたユーザーを削除する。" do
user = User.create! valid_attributes
expect {
delete :destroy, {:id => user.to_param}
}.to change(User, :count).by(-1)
end
it "ユーザー一覧へリダイレクトする。" do
user = User.create! valid_attributes
delete :destroy, {:id => user.to_param}
expect(response).to redirect_to(users_url)
end
end
以上で、scaffoldで作られたspecファイルとほぼ同じものを作ることができました。
次回は、ログイン機能を追加して、そのテストに挑戦です。