Rspecでビヘイビア(振舞)駆動開発をしよう。でもテストの仕方がわからないとできませんね。今回は、コントローラーのテストでログインに挑戦です。
前提条件
- 「Rspecのインストールとテストの基本」ほぼ全て
- scaffoldでuser作成、Userモデルでのバリデート (「Rspecでモデルのテスト」)
- UsersControllerの変更 (「Rspecでコントローラーのテスト その1」)
ご自分で試してみたい方には以上の作業が必要です。
今回は以下のことを行います。
- Deviseをインストールし、ログインできるようにする
- Rspecでログインできるようにする
- 管理者がログインしてテストする
Deviseのインストールと設定
インストール
- Gemfile gem ‘devise’ を追加
- ターミナル(またはコマンドプロンプト)
以上でインストールができました。
設定
続けて設定を行います。
ターミナル(またはコマンドプロンプト)
を実行することで config/routes.rb と app/models/user.rb に必要事項を挿入し、migrationファイルを用意してくれます。db:migrate します。
おっと、emailがぶつかってしまいました。
db/migrate/********_add_devise_to_users.rb のemailを設定する1行を削除して、もう一度db:migrateします。今度は成功しました。
各ファイルにそれぞれ記入
- config/environments/development.rb
- app/views/layouts/application.html.erb
その他rootの設定とかもありますが、ここでは省略します(Devise で認証機能を追加などを参照してください)。
ApplicationController
app/controllers/application_controller.rb を編集して以下のようにします。
1
2
3
4
class ApplicationController < ActionController::Base
before_action :authenticate_user!
protect_from_forgery with: :exception
end
これで、ログインしなければ何もできなくなりました。rspec spec/controllers/user_controller_spec.rb を実行してみましょう。
すべてがFになり、全て失敗しました。
これらがすべて成功するように変更します。
Rspec でログイン
Rspec でログインできるようになれば解決しそうです。まずRspecでdeviseのメソッドを使えるようにrails_helper.rbに以下の2行を追加します。
1
2
3
4
5
require 'devise'
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
end
次に、spec/support/ の中に controller_macros.rb を作り以下を記述します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module ControllerMacros
def login_admin
before(:each) do
@request.env["devise.mapping"] = Devise.mappings[:admin]
admin = FactoryGirl.create(:admin, role: FactoryGirl.create(:role_admin))
sign_in admin
end
end
def login_user
before(:each) do
@request.env["devise.mapping"] = Devise.mappings[:user]
user = FactoryGirl.create(:user, role: FactoryGirl.create(:role_user))
sign_in user
end
end
end
このControllerMacrosを使うよう先ほどのrails_helper.rbに2行追加します。
1
2
3
4
5
6
7
require 'devise'
require 'support/controller_macros'
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
config.extend ControllerMacros, :type => :controller
end
では、users_controller_spec.rb でログインしましょう。login_adminをRSpec.describe UsersController, type: :controller doの行の下に記入します。
1
2
3
4
5
6
7
8
9
10
require 'rails_helper'
RSpec.describe UsersController, type: :controller do
login_admin
let(:valid_attributes) {
FactoryGirl.attributes_for(:user, role: FactoryGirl.create(:role_user))
}
以下省略
rspec spec/controllers/user_controller_spec.rb を実行してみます。
2個の失敗がありましたが、それ以外はログインできたことで成功しています。失敗の原因を突き止めて解決していきましょう。
ログインの影響
失敗のメッセージを見ると期待していた値が「User-1」一人だけだったのに対して受っとった値は「User-1」とログインで作られた「Admin」の二人になったことが原因です。spec/controllers/users_controller_spec.rbの18行目を編集します。
編集前
編集後
マッチャー inculdeを使いました。対象の配列や文字列の中にオブジェクトが含まれていることにマッチします。
Deviseの影響
属性passwordが更新されていないのでしょうか。実はDeviseはpasswordフィールドを使わずencrypted_passwordフィールドに値を暗号化して(暗号化はデフォルトでBcryptを使っています)格納します。そのためパスワードが正しいものであるかを確認するメソッドが用意されています。 valid_password?
です。このメソッドを使ってusers_controller_spec.rbを書きかえます。
1
expect(user.password).to eq("new_user_PASSWORD")
この93行目を次のように書き換えます。
1
2
3
4
5
6
7
8
9
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.valid_password?("new_user_PASSWORD")).to eq(true)
expect(user.email).to eq("new_user@example.com")
end
rspecを実行してみます。
すべて成功しました。
user_spec.rbの修正と複数スペックの実行
spec/models/user_spec.rb にも先ほどと同じuserのpassword属性に対する変更が必要です。
のようにテストしていた箇所を次のように変更します。
rspec spec/models/user_spec.rb を実行してみます。
email と passward について同じエラーメッセージが2つずつ取得されています。modelでバリデートした結果とdeviseがバリデートした結果のものと思われるので、ここではmodelのバリデートをコメントにしておくことにします。
1
2
3
4
5
6
7
8
9
10
11
12
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
belongs_to :role
validates :role, presence: true
validates :name, length: {minimum: 2, maximum: 64, if: "name.present?"}, format: { with: /\A[^<>]*\z/ }, uniqueness: true, presence: true
#validates :email, presence: true
#validates :password, presence: true
end
devise の設定が追加されていますが、ここでは詳しくは触れません。デフォルトの設定のままです。
以上で、
- spec/models/roles_spec.rb
- spec/models/users_spec.rb
- spec/controllers/users_controller_spec.rb
がすべて成功するようになったと思います。
が、単独で各スペックを実行するときには成功しますが、users_spec.rb と users_controller_spec.rb を合わせてテストすると:userファクトリーで使ったsequenceが連続した値を使うため期待した値にならないことがわかりました。そこで、コントローラーで:userファクトリーを使うときに、sequenceの値をリセット( FactoryGirl.reload
)して「1」から始まるように変更しました。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
it "一般ユーザが登録できる。" do
FactoryGirl.reload
FactoryGirl.create(:user, role: FactoryGirl.create(:role_user))
users = User.all
expect(users.size).to eq(1)
expect(users[0].name).to eq("User-1")
expect(users[0].role.role_name).to eq("general_user")
end
it "一般ユーザーが複数登録できる。" do
FactoryGirl.reload
user_role = FactoryGirl.create(:role_user)
FactoryGirl.create(:user, role: user_role)
FactoryGirl.create(:user, role: user_role)
FactoryGirl.create(:admin, role: FactoryGirl.create(:role_admin))
FactoryGirl.create(:user, role: user_role)
users = User.all
expect(users.size).to eq(4)
expect(users[0].name).to eq("User-1")
expect(users[1].name).to eq("User-2")
expect(users[3].name).to eq("User-3")
end
ハイライトした行が修正した箇所です。これで、
でも成功できました。
FactoryGirl.reload
は、FactoryGirl全体を初期化してしまうので大ナタを振るいすぎている気もしますが。。。
次回は、request spec に挑戦しようと思います。