認証を必要とするいくつかの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全体の承認のための保守可能なソリューションではありません。ヘッダー名がわずかに変更された場合、すべてのテストを変更する必要があることを想像してください。
注:この回答は、あなたが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
は、そのようなリクエストを行うたびにヘッダーに追加します。
よくある誤解は、コントローラーを扱い、テストを同等に要求することです。
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のプルリクエスト は、デフォルトのヘッダーの動作を追加しようとするため、リクエストの仕様でデフォルトのヘッダーを使用する場合は、このようなアプローチを試すこともできます。
おそらくRspecがspecファイルをどのように処理するかが原因です。それ ファイルの場所から仕様タイプを自動的に推論しなくなりました
この動作を以前に知っていたものに設定してみてください
RSpec.configure do |config|
config.infer_spec_type_from_file_location!
end
または、プロジェクト内の各コントローラー仕様ファイルに対してローカルに設定します
describe MyController, type: :controller do
# your specs accessing @request
end
スーリヤの答えは最高です。しかし、あなたは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
によってリクエストメソッドを呼び出します。
要求を認証する関数をスタブして、trueまたは関数によって返される値を返します。
ApplicationController.any_instance.stub(:authenticate_request) { true }