wake-up-neo.com

Setzen Sie den Header in der RSpec 3-Anforderung

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. 

23
lightswitch05

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.

39
Surya

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.

14
Lucas

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
4
Guillaume Petit

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.

2
Tobias

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 }
0
Lavixu