2014-09-12 55 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 } 
相關問題