Posts Rspec でコントローラーのテスト その1
Post
Cancel

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

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
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) という形式でも呼び出せます。

File: spec/controllers/users_controller_spec.rb#index
>
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} のようにブロックで渡します。

File: spec/controllers/users_controller_spec.rb
>
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 とは「前後関係、文脈、周囲の情況、背景 」といった意味なので、場合を分けてテストをするときに使います。

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

  context "正常な値ではない時" do
    it "登録できない。"
  end
end

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

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

File: spec/controllers/users_controller_spec.rb
>
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」だけ増えることを使います。

File: spec/controllers/users_controller_spec.rb#create
>
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はない)属性はコントローラーでは受け取れないと思ってください)。

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

File: app/controllers/users_controller.rb
>
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 のテスト

  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
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の時と同様に「正常な値」の場合と「正常な値ではない」場合に分けて整理すると、次にようになります。

File: spec/controllers/users_controller_spec.rb#update
>
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の時と同じようにできます。

File: spec/controllers/users_controller_spec.rb#update
>
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件減ったこと、ユーザー一覧へリダイレクトされることをテストします。

File: spec/controllers/users_controller_spec.rb#destroy
>
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ファイルとほぼ同じものを作ることができました。

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

シェア
#内容発言者

パスワード認証は、やはり、危険!

Rspec でコントローラーのテスト その2

坂井和郎