2014-09-12 93 views
23

我正在尝试为需要身份验证的一些RSpec请求设置标头。标题是ACCESS_TOKEN。无论我如何设置标题,它都不会被设置。我知道该应用程序的作品,因为我可以手动测试它,我只是不能让rspec测试工作。请参阅完整源代码&此处测试此问题:https://github.com/lightswitch05/rspec-set-header-example在RSpec 3请求中设置标题

由于身份验证用于大多数请求规范,因此我创建了支持帮助程序模块来检索访问令牌并将其设置在标题中。下面是如何我想设置页眉的总结,看到的一切,我已经试过在使用这个助手,并应工作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 

我知道我可以手动设置头中的个体getpost请求 - 但是,这并不是API级权限的维护的解决方案。想象一下,如果标题名称稍有改变,则必须更改每个测试。

回答

39

注意:这个答案是基于你似乎打电话给api_v1_session_pathpost请求SessionsController为你想要在你的请求规格中运行的每个规范。

有两种方法可以解决我认为你在这里遇到的问题。

解决方案#1 - 无论您在SessionHelper创建另一个helper方法,或者在其他一些辅助性文件,称为支持/ requests_helper.rb(无论你喜欢)。我在支持创建另一个帮手/ 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 - 更改规格/工厂/ 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 

我会去与“解决方案#1”,因为它消除了让您记住每次发送HTTP_ACCESS_TOKEN报头时的负担,每次您要发出这样的请求。

+0

我对Solution#1进行了一些稍微更改:在rails_helper中,我将辅助函数包含为控制器类型。我必须在我的助手中添加这个标记,就像这个request.headers ['HTTP_AUTH_TOKEN'] = Rails.application.secrets.default_api_token – 2015-07-23 14:10:44

+3

对于任何想要使用RSpec 3和Rails 4添加标题的人,只需调用'request.headers .merge!({'X-MYHEADER':'value'})'在'get','post'等之前的任何位置添加标题到控制器规范的请求中。有关示例,请参阅此要点https://gist.github.com/quadrolls/9203f924f934398f6992162bbbcb1a0c – Squadrons 2016-04-07 20:36:26

4

可能是因为现在Rspec如何处理规格文件。它no longer automatically infers spec type from a file location

尝试要么设置此行为回到你以前知道

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

或设置在本地的项目中的每个控制器规格文件

describe MyController, type: :controller do 
    # your specs accessing @request 
end 
+1

虽然将请求规范更改为控制器规范将允许访问'request' var - 这不会回答DRYest在** request **规范中设置标头的方法。 – lightswitch05 2014-09-15 13:01:19

14

常见的误解是治疗控制器是什么并同样要求测试。

从阅读关于controller specsrequest specs开始将是一件好事。如您所见,控制器规格模拟http请求,而请求规范执行完整堆栈请求。

你可以找到一些关于为什么你应该写控制器规格以及在那里测试什么的好文章here。尽管写它们是件好事,但我认为它们不应该碰到数据库。

因此,尽管Voxdei answer部分有效(在将控制器规范的请求规范更改为您设置标头的方式将起作用),但在我看来,它忽略了这一点。

在要求的规格,你不能只用请求/控制器方法,你必须通过你的头在哈希作为您的请求方法的第三个参数,所以即

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

什么,你可以,虽然做的是短线认证,如:

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

然后,你可以在一个规范测试认证,以确保它的工作原理,并在其他规格滤波器之前使用这些。这也可能是更好的方法,因为每次运行需要验证的spec时执行额外的请求都是非常大的开销。

我也发现很有趣pull request in grape gem它试图添加默认标题行为,所以你也可以尝试这种方法,如果你真的想在请求规格中使用默认标题。

2

苏里亚的答案是最好的。但是你可以多一点有点干起来:

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

在这里你只有一个方法,并通过给定参数method调用请求的方法。

0

我将验证请求的函数stub返回true或函数返回的任何值。

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