RequestのRSpecを実装する

Railsのテストを書く上で,やっぱりRSpecはめちゃくちゃ書きやすい.
そんな中でも最近はcontrollerのspecはあまり書かなくて,requestのspecばかりを書いている.

そもそもcontrollerのspecは単体テストで,requestのspecは結合テストなのだけれど,だいたい同じ部分のテストになってしまうので,片方だけ書いたら割りと満足しています.


というわけでrequestのテストを書いてみます.
ちなみにFactoryGirlを使って色々生成しているので,その部分のfactoryは書いてあるものとします.

scaffoldで生成されるデフォルトのアクション

まずは普通に生成されるアクションについてのテストです.
StaticControllerについて,index,show,new,edit,create,update,destroyのそれぞれのアクションのテストを書きます.

require 'rails_helper'

RSpec.describe "Statics", type: :request do
  include Rack::Test::Methods

  before(:each) do
    @static = FactoryGirl.create(:static)
    @params = { static: FactoryGirl.attributes_for(:static) }
  end

  describe "GET /statics" do
    it "works!" do
      get statics_path
      expect(last_response.status).to eq(200)
    end
  end
  describe "GET /statics/new" do
    it "works!" do
      get new_static_path
      expect(last_response.status).to eq(200)
    end
  end
  describe "GET /statics/1/edit" do
    it "works!" do
      get edit_static_path(@static)
      expect(last_response.status).to eq(200)
    end
  end
  describe "POST /statics/create" do
    it "should create" do
      post statics_path, @params
      ## 成功した場合デフォルトではredirect_toされる
      ## 成功したかの確認はリダイレクトとerrorsの中身で確認する
      expect(last_response.status).to eq(302)
      expect(last_response.errors).to eq("")
    end
  end
  describe "PUT /statics/1/update" do
    it "should update" do
      put static_path(@static), @params
      expect(last_response.status).to eq(302)
      expect(last_response.errors).to eq("")
    end
  end
  describe "DELETE /statics/1/destroy" do
    it "should delete" do
      delete static_path(@static)
      expect(last_response.status).to eq(302)
      expect(last_response.errors).to eq("")
    end
  end
end


コントローラをscaffoldで生成した場合,create,update,destroyアクションは,成功時にリダイレクトをかけます.
なのでstatusが302になっているのが正しい挙動です.

デフォルトで生成されるrequestのspecでは,last_responseではなく,responseで判定しています.

expect(response).to have_http_status(200)

これでもいいのですが,postやputアクションのときに困ります.
成功時にリダイレクトされるので,statusは302になるのですが,それだけでチェックするのがちょっと不安…….
というわけでerrorsの中身か,もしくはbodyの中身をチェックしたいのです.

statusが200のときは,response.bodyを見ればいいだけの話ですが,statusが302のときはbodyにはYou are being redirectedが入っているだけです.

そのため,

include Rack::Test::Methods

として,テストメソッドを別途読み込みし,last_responseが使えるようにしています.
last_responseには,errorsが含まれるので,bodyがチェックできない代わりにerrorsの中身をチェックして満足することにしました.


ログインが必要になるコントローラのテスト

ログインが必要になる場合には,予めログインさせておいた上で,上記のようなテストを組む必要があります.


まず,ログイン用のメソッドを作っておきます.
ちなみにログインするモデルはUserとしてあります.

/spec/support/request_helper.rb

include Warden::Test::Helpers
module RequestHelpers
  def login_user(user)
    login_as user, scope: :user
  end
end


追加したログイン用メソッドを読み込む必要があるため,rails_helper.rbに追記します.

/spec/rails_helper.rb

# 中略
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }

RSpec.configure do |config|
  ## 中略
  config.include Devise::TestHelpers, :type => :controller
  config.include RequestHelpers, :type => :request
  config.include Capybara::DSL
end

そしてテスト本体を書きます.

/spec/requests/users/statics_spec.rb

require 'rails_helper'

RSpec.describe "Users::StaticsController", type: :request do

  describe "before login" do
    describe "GET /users/statics" do
      it "should redirect" do
        get users_statics_path
        expect(response).to have_http_status(302)
      end
    end
  end

  describe "after login" do
    before(:each) do
      @user = FactoryGirl.create(:user)
      login_user @user
    end

    describe "GET /users/statics" do
      it "works!" do
        get users_statics_path
        expect(response).to have_http_status(200)
      end
    end

    # 残りは省略
  end
end


こんな感じで,ログイン前にはsign_inにリダイレクトがかかるため,そのテストをしています.
ログイン後は,同じアドレスでアクセスできることを確認しています.

こちらもpostやputのテストをする場合は,

include Rack::Test::Methods

して,last_responseを使えるようにします.

参考


Rails でつくる API のテストの書き方(RSpec + FactoryGirl) - 彼女からは、おいちゃんと呼ばれています


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