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é.
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.
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.
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
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
.
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 }