マルチセッションの request spec を書く

# request spec とは
controller spec との違いが最初はわかりづらいかもしれませんが、 controller spec は ``PostsController`` などの単体テストを書くためのもので、以下のような特徴があります。

- ``get :index`` などと書くが、第一引数はアクション名であり、実際の url とはまったく関係がない。
- ``get :show, id: 3`` のようにして、 ``params[:id]`` に値を直接渡すことができる。
- ``post :create, {}, {user_id: 5}`` のようにして、 ``session[:user_id]`` に値を直接渡すことができる。
- デフォルトでは、 view ファイルの中身は評価されない。 (``render_template`` マッチャでどの view ファイルが render されるかをテスト出来るが、 中身は評価されないので partial の読み込みまでは確認出来ない。この挙動は ``render_views`` 宣言によって変更可能)
- ``spec/controllers/`` 以下に置くか、もしくは ``type: :controller`` 宣言をする。

一方、 request spec は、実際にブラウザの挙動をシミュレートするのに近いです。

- ``get '/users/2'`` などと、実際の path を書く必要がある。
- ``params`` や ``session`` に値を渡すことができないので、サインインなどの手続きをそのままする必要がある。
- ``spec/requests/`` 以下に置くか、もしくは ``type: :request`` 宣言をする。

# マルチセッションの書き方
以下のようなテストを書くことは出来るのでしょうか？

- ユーザAが post を編集中にユーザBが同じ post に編集を加えて先に保存
- ユーザAがユーザBの post にコメントした後、ユーザBがサインインすると通知を受け取る
- ユーザAがルームを作成し、ユーザBがそこにアクセスすると、ユーザAの画面にユーザBの名前が表示される

はい、出来ますってことで、``Rack::Test::Methods``を include してやると、 ``with_session`` ていう便利メソッドが使えるようになるのでそれでごりごり書けばおｋ。
``with_session`` の引数は単なる名前なので何でもよくて、同じ名前を指定すれば同じセッションの続きを書くことが出来る。

```rails_helper.rb
RSpec.configure do |config|
  config.include Rack::Test::Methods, type: :request
end
```
```ruby
require 'rails_helper'

RSpec.describe 'multi session', type: :request do
  let(:user1){ FactoryGirl.create(:user) }
  let(:user2){ FactoryGirl.create(:user) }
  it do
    with_session(:taro) do
      post '/sign_in', user_name: user1.name, password: user1.password
      post '/rooms', name: "taro's room"
      json ||= JSON.parse(last_response.body)
      @room_id = json['id']
    end
    with_session(:jiro) do
      post '/sign_in', user_name: user2.name, password: user2.password
      post "/rooms/#{@room_id}/join"
    end
    with_session(:taro) do
      get "/rooms/#{@room_id}"
      json ||= JSON.parse(last_response.body)
      expect(json['members'].include?(user2.name)
    end
  end
end
```
最初からこのコードだけ見せればこれ以上説明いらない感じですね。

# おまけ
宣伝がてらで、私が趣味で作ってる [人狼アリーナ](http://jinro-arena.herokuapp.com)のテストコードを晒しておきます。 twitter oauth をモックしたりしているのでご参考ください。

```rails_helper.rb
RSpec.configure do |config|
  config.include MultiSession, type: :request
end
```
```spec/support/multi_session.rb
module MultiSession
  include Rack::Test::Methods

  def login_twitter
    OmniAuth.config.test_mode = true
    uid = FactoryGirl.generate(:uid)
    OmniAuth.config.mock_auth[:twitter] = {
      provider: 'twitter',
      uid: uid,
      info: {name: "twitter_#{uid}"},
      extra: { raw_info: {} }
    }.with_indifferent_access
    get "/s/auth/twitter/callback"
    Auth.find_by(uid: uid.to_s).user
  end
  def json
    @json ||= JSON.parse(last_response.body)
  end
end
```
```spec/requests/wolf_spec.rb
require 'rails_helper'

describe Creature::Wolf do
  let(:village){ FactoryGirl.create(:village, :playing, days_count: 2, regulation:{wolf: 1, possessed: 1, body_guard: 1, seer: 1}) }
  let(:wolf){ village.creatures_a.type(:wolf)[0] }
  let(:possessed){ village.creatures_a.type(:possessed)[0] }
  let(:body_guard){ village.creatures_a.type(:body_guard)[0] }
  let(:seer){ village.creatures_a.type(:seer)[0] }
  before do
    with_session(:possessed)  { possessed.update_column(:user_id, login_twitter.id) }
    with_session(:body_guard) { body_guard.update_column(:user_id, login_twitter.id) }
    with_session(:seer)       { seer.update_column(:user_id, login_twitter.id) }
    with_session(:wolf)       { wolf.update_column(:user_id, login_twitter.id) }
  end

  describe '襲撃' do
    def update_actions(hash)
      patch "/s/villages/#{village.id}/days/1.json", {actions: hash}
    end
    before do
      with_session(:possessed)  { update_actions(vote: body_guard.id) }
      with_session(:body_guard) { update_actions(vote: possessed.id, guard: seer.id) }
      with_session(:seer)       { update_actions(vote: possessed.id, see: wolf.id) }
      with_session(:wolf)       { update_actions(vote: possessed.id, bite: bite_id) }
    end
    context '投票も護衛もされていない狩人を襲撃' do
      let(:bite_id){ body_guard.id }
      it '勝利する' do
        with_session(:wolf) do
          expect(json['village']['days'][1]['victim_ids']).to eq [body_guard.id]
          expect(json['village']['days'][1]['notices'][wolf.id.to_s]['bite']).to eq body_guard.id
          expect(json['village']['winner_side']).to eq 'wolf'
          expect(json['messages']['epilogue']).to include ['winner_side', '人狼が勝利しました。', '人狼が勝利しました。']
        end
      end
    end
    context '投票対象を襲撃' do
      let(:bite_id){ possessed.id }
      it '失敗する' do
        with_session(:wolf) do
          expect(json['village']['days'][1]['victim_ids']).to eq []
          expect(json['village']['days'][1]['notices'][wolf.id.to_s]['bite']).to eq 'fail'
        end
      end
    end
    context '護衛対象を襲撃' do
      let(:bite_id){ seer.id }
      it '失敗する'  do
        with_session(:wolf) do
          expect(json['village']['days'][1]['victim_ids']).to eq []
          expect(json['village']['days'][1]['notices'][wolf.id.to_s]['bite']).to eq 'fail'
        end
      end
    end
  end
end
```