Ich versuche, den Header für einige RSpec-Anforderungen festzulegen, für die eine Authentifizierung erforderlich ist. Der Header lautet ACCESS_TOKEN
. Egal wie ich versuche, den Header festzulegen, er wird nie gesetzt. Ich weiß, dass die App funktioniert, weil ich sie manuell testen kann. Ich bekomme nur keine rspec-Tests, um zu funktionieren. Den vollständigen Quellcode und die Tests für dieses Problem finden Sie hier: https://github.com/lightswitch05/rspec-set-header-example
Da in den meisten meiner Anforderungsspezifikationen Authentifizierung verwendet wird, habe ich ein Unterstützungshilfemodul erstellt, um ein Zugriffstoken abzurufen und es im Header festzulegen. Im Folgenden finden Sie eine Zusammenfassung, wie ich versuche, die Kopfzeile festzulegen. Ich sehe alles, was ich in der full source ausprobiert habe - /
# 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
ein Beispiel für eine Anforderungsspezifikation, die diesen Helfer verwendet und sollte funktionieren, schlägt jedoch immer fehl, da der Header nie gesetzt wird:
# 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
Ich weiß, ich kann den Header in den einzelnen get
- und post
-Anforderungen manuell festlegen. Dies ist jedoch keine wartbare Lösung für die API-weite Autorisierung. Stellen Sie sich vor, Sie müssen jeden Test ändern, wenn sich der Name des Headers geringfügig geändert hat.
Hinweis: Diese Antwort basiert auf dem, was Sie scheinbar api_v1_session_path
mit post
request an SessionsController
für jede Spezifikation aufrufen, die Sie in Ihren Anforderungsspezifikationen ausführen möchten.
Es gibt zwei Wege, um das Problem zu lösen, von dem ich dachte, dass Sie es hier haben.
Lösung # 1 - Entweder Sie erstellen eine andere Hilfsmethode in Ihrer SessionHelper
oder in einer anderen Hilfsdatei mit dem Namen support/request_helper.rb (je nach Wunsch). Ich würde einen weiteren Helfer in support/anfragen_helper.rb erstellen:
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
dann in Rails_helper.rb:
# Include the sessions helper
config.include SessionHelper, type: :request
# Include the requests helper
config.include RequestsHelper, type: :request
Ändern Sie 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
Jetzt können Sie alle Ihre Anforderungsspezifikationen wie folgt ändern:
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
Lösung # 2 - Spezifikationen/factories/access_token_factory.rb ändern in:
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
Ändern Sie nun alle Anforderungsspezifikationen, um access_token
zu verwenden:
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
Ich würde mit " Solution # 1 " fortfahren, da Sie dadurch nicht mehr daran denken müssen, HTTP_ACCESS_TOKEN
in Kopfzeilen zu senden, wenn Sie solche Anfragen stellen möchten.
Ein allgemeines Missverständnis besteht darin, Controller- und Anforderungstests gleich zu behandeln.
Es ist gut, mit dem Lesen von Controller-Spezifikationen und Request-Spezifikationen zu beginnen. Wie Sie sehen, simulieren Controller-Spezifikationen eine HTTP-Anforderung, während Anforderungsspezifikationen eine vollständige Stack-Anforderung ausführen.
Einige gute Artikel darüber, warum Sie Controller-Spezifikationen schreiben sollten und was Sie dort testen sollten, finden Sie hier . Obwohl es gut ist, sie zu schreiben, sollten sie meiner Meinung nach nicht die Datenbank berühren.
Obwohl Voxdei answer teilweise gültig ist (nachdem die Anfragespezifikationen an Controller-Spezifikationen geändert wurden, funktioniert die Methode, mit der Header gesetzt werden), verfehlt jedoch meiner Meinung nach der Punkt.
In Anforderungsspezifikationen können Sie nicht einfach Anforderungs-/Controller-Methoden verwenden. Sie müssen Ihre Header in Hash als drittes Argument Ihrer Anforderungsmethoden übergeben, d. H.
post '/something', {}, {'MY-HEADER' => 'value'}
Was Sie jedoch tun können, ist Stub-Authentifizierung wie:
before do
allow(AccessToken).to receive("authenticate").and_return(true)
end
Dann können Sie Ihre Authentifizierung in einer Spezifikation testen, um sicherzustellen, dass sie funktioniert, und diese vor dem Filtern in anderen Spezifikationen verwenden. Dies ist wahrscheinlich auch ein besserer Ansatz, da die Durchführung zusätzlicher Anforderungen bei jeder Ausführung, für die eine Authentifizierung erforderlich ist, einen erheblichen Aufwand bedeutet.
Ich habe auch eine ziemlich interessante pull-Anfrage in grape gem gefunden, die versucht, das Verhalten von Standard-Headern hinzuzufügen. Sie können also einen solchen Ansatz ausprobieren, wenn Sie wirklich Standard-Header in Anforderungsspezifikationen verwenden möchten.
Vermutlich, weil Rspec nun Spezifikationsdateien behandelt. Es leitet den Spezifikationstyp nicht mehr automatisch von einem Dateispeicherort ab .
Versuchen Sie, dieses Verhalten auf das zurückzusetzen, was Sie früher gewusst haben
RSpec.configure do |config|
config.infer_spec_type_from_file_location!
end
oder legen Sie sie lokal für jede Controller-Spezifikationsdatei in Ihrem Projekt fest
describe MyController, type: :controller do
# your specs accessing @request
end
Suryas Antwort ist das Beste. Aber Sie können DRY noch ein bisschen mehr:
def request_with_user_session(method, path, params={}, headers={})
headers.merge!('HTTP_ACCESS_TOKEN' => retrieve_access_token)
send(method, path, params, headers)
end
Hier haben Sie nur eine Methode und rufen die Request-Methode mit dem angegebenen Parameter method
auf.
Ich stuble die Funktion, die die Anforderung authentifiziert, um true oder einen von der Funktion zurückgegebenen Wert zurückzugeben.
ApplicationController.any_instance.stub(:authenticate_request) { true }