RSpec
RSpecDay 3

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

More than 3 years have passed since last update.


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 を書く必要がある。


  • paramssession に値を渡すことができないので、サインインなどの手続きをそのままする必要がある。


  • spec/requests/ 以下に置くか、もしくは type: :request 宣言をする。


マルチセッションの書き方

以下のようなテストを書くことは出来るのでしょうか?


  • ユーザAが post を編集中にユーザBが同じ post に編集を加えて先に保存

  • ユーザAがユーザBの post にコメントした後、ユーザBがサインインすると通知を受け取る

  • ユーザAがルームを作成し、ユーザBがそこにアクセスすると、ユーザAの画面にユーザBの名前が表示される

はい、出来ますってことで、Rack::Test::Methodsを include してやると、 with_session ていう便利メソッドが使えるようになるのでそれでごりごり書けばおk。

with_session の引数は単なる名前なので何でもよくて、同じ名前を指定すれば同じセッションの続きを書くことが出来る。


rails_helper.rb

RSpec.configure do |config|

config.include Rack::Test::Methods, type: :request
end

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

最初からこのコードだけ見せればこれ以上説明いらない感じですね。


おまけ

宣伝がてらで、私が趣味で作ってる 人狼アリーナのテストコードを晒しておきます。 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