web-dev-qa-db-fra.com

Définir l'en-tête dans la requête RSpec 3

J'essaie de définir l'en-tête pour certaines requêtes RSpec nécessitant une authentification. L'en-tête est ACCESS_TOKEN. Peu importe la façon dont j'essaie de définir l'en-tête, il ne l'est jamais. Je sais que l'application fonctionne parce que je peux la tester manuellement. Je ne peux tout simplement pas faire fonctionner les tests rspec. Voir le code source complet et les tests pour ce problème ici: https://github.com/lightswitch05/rspec-set-header-example

Étant donné que l'authentification est utilisée dans la plupart de mes spécifications de demande, j'ai créé un module d'assistance pour récupérer un jeton d'accès et le définir dans l'en-tête. Vous trouverez ci-dessous un résumé de la manière dont j'essaie de définir l'en-tête. Voir tout ce que j'ai essayé dans le source complète

# 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

un exemple de spécification de demande qui utilise cet assistant et devrait fonctionner, mais échoue toujours car l'en-tête n'est jamais défini:

# 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

Je sais que je peux définir manuellement l'en-tête dans les demandes individuelles get et post - mais ce n'est pas une solution maintenable pour l'autorisation à l'échelle de l'API. Imaginez avoir à changer chaque test si le nom de l'en-tête a légèrement changé. 

23
lightswitch05

Remarque: cette réponse est basée sur ce que vous semblez appeler api_v1_session_path avec une demande post à SessionsController pour chaque spécification que vous essayez d'exécuter dans vos spécifications de demandes.

Il y a deux façons de résoudre le problème que je pensais avoir ici.

Solution n ° 1 - Vous créez une autre méthode d'assistance dans votre SessionHelper ou dans un autre fichier d'aide appelé support/orders_helper.rb (comme vous préférez). Je créerais un autre assistant dans support/request_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

puis dans Rails_helper.rb:

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

changez 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

Maintenant, vous pouvez modifier toutes vos spécifications de demandes comme ceci:

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

Solution n ° 2 - Modifiez specs/factories/access_token_factory.rb en:

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

Maintenant, modifiez vos spécifications de toutes les demandes pour utiliser 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

J'irais avec " Solution n ° 1 " car cela vous évite d'avoir à vous rappeler que vous devez envoyer HTTP_ACCESS_TOKEN dans les en-têtes chaque fois que vous souhaitez faire de telles demandes.

39
Surya

Une idée fausse commune est de traiter le contrôleur et de demander des tests de manière égale. 

Il serait bon de commencer par lire à propos de specs de contrôleur et request specs . Comme vous pouvez le constater, les spécifications du contrôleur simulent une requête http, tandis que les spécifications de la requête effectuent une requête de pile complète.

Vous pouvez trouver un bon article sur les raisons pour lesquelles vous devriez écrire les spécifications du contrôleur et ce qu'il faut tester ici ici . Bien qu'il soit bon de les écrire, ils ne devraient pas toucher à la base de données à mon avis.

Ainsi, bien que Voxdei answer soit partiellement valide (après avoir modifié les spécifications de requête en spécifications de contrôleur, votre méthode de définition des en-têtes fonctionnera), mais, à mon avis, elle passe à côté de l'essentiel.

Dans les spécifications de demande, vous ne pouvez pas simplement utiliser des méthodes requête/contrôleur, vous devez passer vos en-têtes dans hash en tant que troisième argument de vos méthodes de requête.

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

Ce que vous pourriez faire, c’est stub authentifier comme:

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

Ensuite, vous pouvez tester votre authentification dans une spécification pour vous assurer de son bon fonctionnement et utiliser un tel filtre avant filtre dans d’autres spécifications. C’est aussi probablement une meilleure approche, car effectuer des requêtes supplémentaires chaque fois que vous exécutez une spécification nécessitant une authentification est une lourde charge supplémentaire.

J'ai aussi trouvé une requête pull assez intéressante dans raisin gem qui tente d'ajouter le comportement des en-têtes par défaut afin que vous puissiez également utiliser une telle approche si vous souhaitez vraiment utiliser les en-têtes par défaut dans les spécifications de la demande.

14
Lucas

Probablement à cause de la manière dont Rspec traite les fichiers de spécifications. Il n'infère plus automatiquement le type de spécification à partir d'un emplacement de fichier

Essayez soit de régler ce comportement sur ce que vous saviez

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

ou le définir localement pour chaque fichier de spécifications de contrôleur de votre projet

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

La réponse de Surya est la meilleure. Mais vous pouvez DRY un peu plus:

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

Ici, vous n’avez qu’une méthode et appelez la méthode request avec le paramètre method.

2
Tobias

Je stub la fonction qui authentifie la demande de retour true ou toute valeur renvoyée par la fonction.

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