web-dev-qa-db-ja.com

RSpec 3リクエストでヘッダーを設定

認証を必要とするいくつかのRSpec要求にヘッダーを設定しようとしています。ヘッダーはACCESS_TOKEN。どのようにヘッダーを設定しようとしても、ヘッダーは設定されません。手動でテストできるため、アプリが機能することはわかっています。rspecテストを機能させることはできません。この問題の完全なソースコードとテストについては、こちらをご覧ください。 https://github.com/lightswitch05/rspec-set-header-example

認証はほとんどの要求仕様で使用されるため、アクセストークンを取得してヘッダーに設定するサポートヘルパーモジュールを作成しました。以下は、ヘッダーを設定しようとしている方法の概要です。 full source

# my_app/spec/support/session_helper.rb
module SessionHelper
  def retrieve_access_token
    post api_v1_session_path({email: '[email protected]', password: 'poor_password'})

    expect(response.response_code).to eq 201
    expect(response.body).to match(/"access_token":".{20}"/)
    parsed = JSON(response.body)
    token = parsed['access_token']['access_token']

    @request.headers['HTTP_ACCESS_TOKEN'] = token
  end
end

このヘルパーを使用して動作するはずのリクエスト仕様の例ですが、ヘッダーが設定されないため常に失敗します。

# my_app/spec/requests/posts_spec.rb
# ...
context "create" do
  it "creates a post" do
    retrieve_access_token
    post = FactoryGirl.build(:post)

    post api_v1_posts_path(
      post: {
        title: post.title,
        content: post.content
      }
    )

    expect(response.body).to include('"id":')
    expect(response.body).to include('"title":"' + post.title + '"')
    expect(response.body).to include('"content":"' + post.content + '"')
    expect(response.response_code).to eq 201
  end
end

個々のgetおよびpostリクエストにヘッダーを手動で設定できることは知っていますが、これはAPI全体の承認のための保守可能なソリューションではありません。ヘッダー名がわずかに変更された場合、すべてのテストを変更する必要があることを想像してください。

23
lightswitch05

注:この回答は、あなたがapi_v1_session_pathリクエストスペックで実行しようとしているすべてのスペックについて、postへのSessionsControllerリクエストを含む。

ここにあると思った問題を解決する方法は2つあります。

ソリューション#1-SessionHelperまたはsupport/requests_helper.rbと呼ばれる他のヘルパーファイルに別のヘルパーメソッドを作成します(ただし、お好みで)。 support/requests_helper.rbに別のヘルパーを作成します。

module RequestsHelper
  def get_with_token(path, params={}, headers={})
    headers.merge!('HTTP_ACCESS_TOKEN' => retrieve_access_token)
    get path, params, headers
  end

  def post_with_token(path, params={}, headers={})
    headers.merge!('HTTP_ACCESS_TOKEN' => retrieve_access_token)
    post path, params, headers
  end

  # similarly for xhr..
end

次にRails_helper.rbで:

  # Include the sessions helper
  config.include SessionHelper, type: :request
  # Include the requests helper
  config.include RequestsHelper, type: :request

session_helper.rbを変更します。

# my_app/spec/support/session_helper.rb
module SessionHelper
  def retrieve_access_token
    post api_v1_session_path({email: '[email protected]', password: 'poor_password'})

    expect(response.response_code).to eq 201
    expect(response.body).to match(/"access_token":".{20}"/)
    parsed = JSON(response.body)
    parsed['access_token']['access_token'] # return token here!!
  end
end

これで、すべてのリクエストの仕様を次のように変更できます。

describe Api::V1::PostsController do

  context "index" do
    it "retrieves the posts" do
      get_with_token api_v1_posts_path

      expect(response.body).to include('"posts":[]')
      expect(response.response_code).to eq 200
    end

    it "requires a valid session key" do
      get api_v1_posts_path

      expect(response.body).to include('"error":"unauthenticated"')
      expect(response.response_code).to eq 401
    end
  end
end

ソリューション#2-specs/factories/access_token_factory.rbを次のように変更します:

FactoryGirl.define do
  factory :access_token do
    active true
  end

  # can be used when you want to test against expired access tokens:
  factory :inactive_access_token do
    active false
  end
end

ここで、すべてのリクエストの仕様を変更して、access_token

describe Api::V1::PostsController do

  context "index" do
    let(:access_token){ FactoryGirl.create(:access_token) }

    it "retrieves the posts" do
      # You will have to send HEADERS while making request like this:
      get api_v1_posts_path, nil, { 'HTTP_ACCESS_TOKEN' => access_token.access_token }

      expect(response.body).to include('"posts":[]')
      expect(response.response_code).to eq 200
    end

    it "requires a valid session key" do
      get api_v1_posts_path

      expect(response.body).to include('"error":"unauthenticated"')
      expect(response.response_code).to eq 401
    end
  end
end

Solution#1」を使用すると、HTTP_ACCESS_TOKENは、そのようなリクエストを行うたびにヘッダーに追加します。

39
Surya

よくある誤解は、コントローラーを扱い、テストを同等に要求することです。

controller specs および request specs について読むことから始めるとよいでしょう。ご覧のとおり、コントローラーの仕様はhttpリクエストをシミュレートしますが、リクエストの仕様は完全なスタックリクエストを実行します。

コントローラーの仕様を記述する必要がある理由と、そこで何をテストするかについての良い記事を見つけることができます here 。それらを書くのは良いことですが、私の意見ではデータベースに触れるべきではありません。

Voxdei answer は部分的に有効ですが(リクエストの仕様をコントローラーの仕様に変更した後、ヘッダーの設定方法は機能します)、私の意見ではポイントを失います。

リクエスト仕様では、リクエスト/コントローラーメソッドを使用することはできません。ヘッダーをリクエストメソッドの3番目の引数としてハッシュで渡す必要があります。

post '/something', {}, {'MY-HEADER' => 'value'}

ただし、次のように認証をスタブすることができます。

before do
  allow(AccessToken).to receive("authenticate").and_return(true)
end

次に、ある仕様で認証をテストして、それが機能することを確認し、他の仕様でフィルター処理する前に認証を使用できます。また、認証を必要とする仕様を実行するたびに追加のリクエストを実行することは非常に大きなオーバーヘッドであるため、これもおそらくより良いアプローチです。

また、非常に興味深い grape gemのプルリクエスト は、デフォルトのヘッダーの動作を追加しようとするため、リクエストの仕様でデフォルトのヘッダーを使用する場合は、このようなアプローチを試すこともできます。

14
Lucas

おそらくRspecがspecファイルをどのように処理するかが原因です。それ ファイルの場所から仕様タイプを自動的に推論しなくなりました

この動作を以前に知っていたものに設定してみてください

RSpec.configure do |config|
  config.infer_spec_type_from_file_location!
end

または、プロジェクト内の各コントローラー仕様ファイルに対してローカルに設定します

describe MyController, type: :controller do
  # your specs accessing @request
end
4
Guillaume Petit

スーリヤの答えは最高です。しかし、あなたはDRYそれをもう少し増やすことができます:

def request_with_user_session(method, path, params={}, headers={})
    headers.merge!('HTTP_ACCESS_TOKEN' => retrieve_access_token)
    send(method, path, params, headers)
end

ここにはメソッドが1つしかなく、指定されたパラメーターmethodによってリクエストメソッドを呼び出します。

2
Tobias

要求を認証する関数をスタブして、trueまたは関数によって返される値を返します。

ApplicationController.any_instance.stub(:authenticate_request) { true }
0
Lavixu