Deviseでログイン状態を用いた振る舞いテストを書く
Everyday RailsのChapter 8「フィーチャスペック」を参考に作ってみます。
フィーチャスペック = 統合テスト
モデルとコントローラの単体テスト、テストデータを作成するためのファクトリを全部まとめて、フィーチャテストを行う。
ちなみにコントローラテストについても議論されているけど、今回自分は書いていない。今回最初の段階でそこに大きな工数を割くことができないということと、フィーチャスペックをきちんと書けば複数の重要なコントローラのテストになりうるから。
テストはoutside-inで書く。つまり
フィーチャスペック(エンドユーザがタスクを完了する流れを想定しながら) -> コントローラ・モデルテストの単体テスト
という流れ。
書いてく
参考記事
Rails + RSpec + Capybara で Devise での認証ログインが必要なインテグレーションテスト(RequestSpec)を行う | EasyRamble
Rails4 にて Devise でユーザー登録・ログイン認証・認可の機能を追加 | EasyRamble
deviseで認証しているログイン必須のRspec controllerテスト - コンユウメモ
テスト
今回使ってくバージョンは3.3.2です。
$ bundle exec rspec -v 3.3.2
platromatecの公式ドキュメント。Test Helperメソッドのところにテスト用のhelperメソッドが載ってるのリファレンス的に確認。
http://devise.plataformatec.com.br/
だけどこれだと、機能テスト(functional test)はできても、統合テスト(integration test)はできない。
結構Web上にある情報がRSpec 2系で古いものが多かったので、
deviseが公式でcapybaraでのintegration testの方法を書いてくれてるので、こちらを参考にする。
How To: Test with Capybara · plataformatec/devise Wiki · GitHub
warden test helpers
というのが必要らしい。
wardenはrackアプリの認証周りのテストをサポートするgemみたい。
例えばこんな感じで簡単にアクセスできるようになる。
user = FactoryGirl.create(:user) login_as(user, :scope => :user)
ということでspec/rails_helper.rb
に以下のように書く。
include Warden::Test::Helpers Warden.test_mode!
ちなみにrails_spec
のconfigureメソッドのブロックの中で以下のように書くと、
config.before :suite do ... end
example実行の一番最初にブロックが一度だけ実行される。
代わりにここでこんな感じに書くのもあり。
config.include Warden::Test::Helpers config.before :suite do Warden.test_mode! end
wardenのhelperであるlogin_as
が使えるようになります。
こんな感じにユーザがログインしている状態をつくるためのメソッドをもつmoduleを作成しておきます。
# spec/supports/controller_macros.rb module ControllerMacros def login_user before(:each) do @request.env["devise.mapping"] = Devise.mappings[:user] user = FactoryGirl.create(:user) sign_in user end end end
こんな感じでspec/supports/request_helpers.rb
を定義します。
include warden::test::helpers module requesthelpers def login(user) login_as user, scope: :user end end
そしたらインテグレーションテストで、さっき独自に定義したlogin()メソッドを使います。
require "rails_helper" RSpec.describe "User" do describe 'Log in/out' do let(:user) { build(:user) } before do login user end it "Log in" do visit new_user_session_path fill_in "Email", with: user.email fill_in "Password", with: user.password click_on "Log in" # after login succeeds expect(page).to have_content 'Logged in as' end end end
ここでlogin
メソッドがないと言われてしまった。
ちゃんとspec/support
ディレクトリを読み込めてないのかも。
wardenが初期設定ちゃんとできてないのかな?改めて確認してみる。
どうやらwardenってDeviseの認証部分の核になってるgemだったらしい。そうなんだ。
devise を知るにはまず warden を知るが良い - vimtaku blog
じゃあもともとwardenは入ってるはずだよね。
最終的な原因は、capybaraがloginしたときにおそらくdeviseのcontrollerの初期設定でリダイレクトされてURLが変わってしまっていたことだった。なるほど...。(これでCapybaraのクラスとメソッド探索したりソース読みに行ったりと意味わからんくらい時間使ってしまった。。)
Capybaraの操作方法は以下のjnchitoさんの記事に非常にお世話になりました。
- 使えるRSpec入門・その4「どんなブラウザ操作も自由自在!逆引きCapybara大辞典」