RSpec, Capybara - OmniAuthのテスト

#経緯
- 先日書いた[Devise+OmniAuthで複数プロバイダ認証](http://qiita.com/mnishiguchi/items/e15bbef61287f84b546e)のテストを書いてみた。
- 今後に活かせるようメモ。

#やり方

##OmniAuthをテストモードにセット

```rb:/spec/rails_helper.rb
#...
OmniAuth.config.test_mode = true
#...
```

##warden's built in stubbing actionsを利用するための設定

- 簡単にログイン・ログアウトができるようにするため。
- 本テストでは`logout :user`のみ使用した。
- 通常の処理を飛ばして即ログイン・ログアウトが可能になる。

```rb:/spec/rails_helper.rb
#...
RSpec.configure do |config|
  #...

  include Warden::Test::Helpers
  Warden.test_mode!

  #...
end
```

#ログイン用ユーティリティー

- [M. Hartl氏](https://www.railstutorial.org/)の[Ruby on Rails Tutorialで学んだもの](https://github.com/mhartl/sample_app_4_0_upgrade/blob/master/spec/support/utilities.rb)を自分なりにアレンジした。
- 同じ関数でコントローラスペックとフィーチャースペックの両方に対応できる。

```rb:/spec/support/utilities.rb
# no_capybara: true\false
def log_in_as(user, options={})
  # For controller specs
  if options[:no_capybara]
    @request.env["devise.mapping"] = Devise.mappings[:user]
    sign_in user
  # For feature specs
  else
    visit new_user_session_path
    fill_in "Email",    with: user.email.upcase
    fill_in "Password", with: user.password
    click_button "Log in"
  end
  user
end
```

##認証データのmockを準備

- 成功と失敗の２パターンを作った。
- Twitterのみでテストする。

```rb:/spec/support/omniauth_utils.rb
def set_omniauth
  OmniAuth.config.mock_auth[:twitter] = OmniAuth::AuthHash.new({
    "provider" => "twitter",
    "uid"  => "mock_uid_1234",
    "info" => {
      "name"  => "Mock User",
      "image" => "http://mock_image_url.com"
    },
    "credentials" => {
       "token"  => "mock_credentials_token",
       "secret" => "mock_credentials_secret"
    },
    "extra" => {
      "raw_info" => {
        "name" => "Mock User",
        "id"   => "mock_uid_1234"
      }
    }
  })
end

def set_invalid_omniauth
  OmniAuth.config.mock_auth[:twitter] = :invalid_credentials
end
```

##フィーチャーテスト

```rb:/spec/features/omniauth_spec.rb
require 'rails_helper'

feature "OmniAuth interface" do

  before { OmniAuth.config.mock_auth[:twitter] = nil }

  describe "new user or non-logged-in user who is not registered with Twitter" do
    let(:submit) { "Sign up / Log in with Twitter" }

    describe "authentication error" do
      before do
        visit root_path
        set_invalid_omniauth
        click_link(submit)
      end

      it { expect(page).to have_content('Could not authenticate you from Twitter') }
    end

    describe "authentication success" do
      before do
        visit root_path
        set_omniauth
        click_link(submit)
      end

      it "asks user for email address" do
        expect(page).to have_content("need your email address")
      end

      describe "filling out the form" do
        let(:continue) { "Continue" }

        describe "with temporary email" do
          before do
            find("#user_username").set "User Example"
            find("#user_email").set "change@me.temporary.com"
            click_button(continue)
          end

          it { expect(page).to have_content("error") }
        end

        describe "with valid information" do
          before do
            find("#user_username").set "User Example"
            find("#user_email").set "user@example.com"
            click_button(continue)
          end

          it { expect(page).to have_content("sent you a confirmation email") }
        end
      end
    end
  end

  describe "non-logged-in user who is registered with Twitter" do
    let(:submit) { "Sign up / Log in with Twitter" }
    let!(:user) do
      user = create(:user)
      attrs = attributes_for(:social_profile)
      attrs["uid"] = "mock_uid_1234"
      user.social_profiles.create(attrs)
      user.confirm!
      user
    end

    before do
      visit root_path
      set_omniauth
      click_link(submit)
    end

    it "can log in with Twitter" do
      expect(current_path).to eq movings_path
      expect(page).to have_content(user.username)
      expect(page).to have_content(user.email)
    end
  end

  describe "logged-in user who is an omniauth first-timer" do
    let(:user) { create(:user) }

    before do
      log_in_as user
      visit root_path
      set_omniauth
    end

    it "not registered with Twitter" do
      expect(page).to have_css(".twitter-connect")
      expect(page).not_to have_css(".twitter-icon")
      expect(user.social_profile(:twitter)).to be_nil
    end

    describe "clicking on Twitter button" do
      before { find(".twitter-connect").click }

      it "connects to Twitter" do
        expect(page).to have_content("Successfully authenticated from Twitter account")
        expect(page).to have_content("Disconnect")
        expect(page).to have_css(".twitter-icon")
        expect(user.social_profile(:twitter)).not_to be_nil
      end

      describe "logging out" do
        let(:submit) { "Sign up / Log in with Twitter" }
        before do
          logout :user
          visit root_path
          set_omniauth
          click_link(submit)
        end

        it "can log in with Twitter" do
          expect(current_path).to eq movings_path
          expect(page).to have_content(user.username)
          expect(page).to have_content(user.email)
        end

        describe "clicking on Twitter button" do
          before { find(".twitter-connect").click }

          it "disconnects from Twitter" do
            expect(page).to have_content("Disconnected")
            expect(page).to have_content("Connect")
            expect(page).not_to have_css(".twitter-icon")
            expect(user.social_profile(:twitter)).to be_nil
          end
        end
      end
    end
  end
end
```

![Screenshot 2015-08-15 09.42.19.png](https://qiita-image-store.s3.amazonaws.com/0/82804/6b924563-b871-8936-c3dc-4a980f1a7ca8.png)

#資料

- https://github.com/intridea/omniauth/wiki/Integration-Testing
- https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara