37
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

RSpecAdvent Calendar 2015

Day 3

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

Posted at

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
37
26
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
37
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?