17987


Web APIでのデータの送受信形式にJSONを使用し、リクエストのContent-
Typeヘッダをapplication/jsonに指定する場合、RequestSpecで以下のように書いてます。

しかし、この書き方だと`spec`ファイル毎に`let(:request_header)`の記入が必要なため、DRYじゃないし、面倒です。  
これをデフォルト設定にする、もしくは汎用的に書く方法を知っている方いたら教えて下さい。

[code]

    describe 'Users' do
    
      let(:request_header) do
        { 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' }
      end
    
      describe 'POST /api/v1/users' do
        it 'creates a new user' do
          params = FactoryGirl.attributes_for(:user)
    
          expect {
            post '/api/v1/users', params.to_json, request_header
          }.to change { User.count }.from(0).to(1)
    
          expect(response.status).to eq(201)
        end
      end
    end
    
[/code]


CC BY-SA 3.0
2015-10-23T07:58:54.337
17979
2015-10-26T05:52:41.053
2015-10-26T05:52:41.053
12927
12927
question
ruby-on-rails ruby rspec
RequestSpecでContent-Typeヘッダapplication/jsonをデフォルトにするには？


下記のissueでも同じような質問が挙げられていますが、完全な自動化は難しいようですね。

[ability to add header on every request spec · Issue #1209 · rspec/rspec-
rails](https://github.com/rspec/rspec-rails/issues/1209)

僕だったら、ショートカット用のメソッドをモジュールとして定義し、request spec全体でincludeするかなーと思います。

[code]

    # spec/support/api_macros.rb
    module ApiMacros
      def post_as_json(path, parameters = nil, headers_or_env = {})
        post path, parameters, headers_or_env.merge('CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json')
      end
    
      # 必要に応じてpost以外のバージョンも定義
    end
    
    # spec/rails_helper.rb
    Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
    # ...
    RSpec.configure do |config|
      # ...
      config.include ApiMacros, type: :request
    end
    
    # spec/requests/users_spec.rb
    require 'rails_helper'
    describe 'Users', type: :request do
      describe 'POST /api/v1/users' do
        it 'creates a new user' do
          params = FactoryGirl.attributes_for(:user)
    
          expect {
            post_as_json '/api/v1/users', params.to_json
          }.to change { User.count }.from(0).to(1)
    
          expect(response.status).to eq(201)
        end
      end
    end
    
[/code]

完全な自動化が無理な以上、落としどころはこんなところではないでしょうか。

## 追記

prependを使うともうちょっとスマートになるかもです。

[code]

    # spec/support/api_macros.rb
    module ApiMacros
      def post(path, parameters = nil, headers_or_env = {})
        super path, parameters, headers_or_env.merge('CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json')
      end
    
      # 必要に応じてpost以外のバージョンも定義
    end
    
    # spec/rails_helper.rb
    Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
    # ...
    RSpec.configure do |config|
      # ...
      config.prepend ApiMacros, type: :request
    end
    
    # spec/requests/users_spec.rb
    require 'rails_helper'
    describe 'Users', type: :request do
      describe 'POST /api/v1/users' do
        it 'creates a new user' do
          params = FactoryGirl.attributes_for(:user)
    
          expect {
            post '/api/v1/users', params.to_json
          }.to change { User.count }.from(0).to(1)
    
          expect(response.status).to eq(201)
        end
      end
    end
    
[/code]

## 追記2

`json: true`みたいなタグを付けて、jsonリクエストを使うかどうか制御するバージョンも作ってみました。

[code]

    # spec/support/api_macros.rb
    module ApiMacros
      def post(path, parameters = nil, headers_or_env = {})
        headers_or_env = headers_or_env.merge('CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json') if @_json
        super path, parameters, headers_or_env
      end
    
      # 必要に応じてpost以外のバージョンも定義
    end
    
    # spec/rails_helper.rb
    Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
    # ...
    RSpec.configure do |config|
      # ...
      config.prepend ApiMacros, type: :request
    
      config.before do |example|
        @_json = example.metadata[:type] == :request && example.metadata[:json]
      end
    end
    
    # spec/requests/users_spec.rb
    require 'rails_helper'
    
    # json: trueが付くと、jsonでリクエストを投げる
    describe 'Users', type: :request, json: true do
      describe 'POST /api/v1/users' do
        it 'creates a new user' do
          params = FactoryGirl.attributes_for(:user)
    
          expect {
            post '/api/v1/users', params.to_json
          }.to change { User.count }.from(0).to(1)
    
          expect(response.status).to eq(201)
        end
      end
    end
    
[/code]

## 追記3

getやdeleteなど、post以外のメソッドも自動的にJSONでリクエストできる仕組みを考えてみました。  
ちょっと強引ですが、 ActionDispatch::Integration::Session#process と
ActionDispatch::Integration::Runner#reset! にモンキーパッチを当てています。  
追記2と同様、spec側では `json: true` のタグでJSONリクエストを使うかどうか制御できます。

[code]

    # spec/support/json_process.rb
    module JsonProcess
      def process(method, path, parameters = nil, headers_or_env = nil)
        headers_or_env = (headers_or_env || {}).merge('CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json') if @_json
        super method, path, parameters, headers_or_env
      end
    end
    
    # spec/rails_helper.rb
    Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
    # ...
    RSpec.configure do |config|
      # ...
      config.before(:example, :json => true) do
        def reset!
          @integration_session = ActionDispatch::Integration::Session.new(app)
          @integration_session.instance_variable_set("@_json", true)
        end
      end
    end
    ActionDispatch::Integration::Session.prepend JsonProcess
    
    # spec/requests/users_spec.rb
    require 'rails_helper'
    
    # json: trueが付くと、jsonでリクエストを投げる
    describe 'Users', type: :request, json: true do
      describe 'POST /api/v1/users' do
        it 'creates a new user' do
          params = FactoryGirl.attributes_for(:user)
    
          expect {
            post '/api/v1/users', params.to_json
          }.to change { User.count }.from(0).to(1)
    
          expect(response.status).to eq(201)
        end
      end
    end
    
[/code]

動作確認時のバージョン

  * Rails 4.2.4
  * rspec-rails 3.3.3


CC BY-SA 3.0
2015-10-23T10:21:12.090
17987
2015-10-23T23:33:49.530
2015-10-23T23:33:49.530
85
85
17979
answer