読者です 読者をやめる 読者になる 読者になる

Deviseでログイン状態を用いた振る舞いテストを書く

RSpec Ruby Rails

Everyday RailsのChapter 8「フィーチャスペック」を参考に作ってみます。

フィーチャスペック = 統合テスト

モデルとコントローラの単体テスト、テストデータを作成するためのファクトリを全部まとめて、フィーチャテストを行う。

ちなみにコントローラテストについても議論されているけど、今回自分は書いていない。今回最初の段階でそこに大きな工数を割くことができないということと、フィーチャスペックをきちんと書けば複数の重要なコントローラのテストになりうるから。

テストはoutside-inで書く。つまり

フィーチャスペック(エンドユーザがタスクを完了する流れを想定しながら) -> コントローラ・モデルテストの単体テスト

という流れ。

書いてく

参考記事

Rails + RSpec + Capybara で Devise での認証ログインが必要なインテグレーションテスト(RequestSpec)を行う | EasyRamble

Rails4 にて Devise でユーザー登録・ログイン認証・認可の機能を追加 | EasyRamble

deviseで認証しているログイン必須のRspec controllerテスト - コンユウメモ

テスト

RSpec最新バージョンは3.3.3

今回使ってくバージョンは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みたい。

hassox/warden · GitHub

例えばこんな感じで簡単にアクセスできるようになる。

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+railsでパスワード認証

どうやらwardenってDeviseの認証部分の核になってるgemだったらしい。そうなんだ。

devise を知るにはまず warden を知るが良い - vimtaku blog

じゃあもともとwardenは入ってるはずだよね。

最終的な原因は、capybaraがloginしたときにおそらくdeviseのcontrollerの初期設定でリダイレクトされてURLが変わってしまっていたことだった。なるほど...。(これでCapybaraのクラスとメソッド探索したりソース読みに行ったりと意味わからんくらい時間使ってしまった。。)

Capybaraの操作方法は以下のjnchitoさんの記事に非常にお世話になりました。

  • 使えるRSpec入門・その4「どんなブラウザ操作も自由自在!逆引きCapybara大辞典」

http://qiita.com/jnchito/items/607f956263c38a5fec24