Reputation: 1551
I'm trying to get my head around the best way to create a user token in my RSpec tests, and write them as eloquently as I can.
Below is one example for my Project class. From the spec below, you'll see I'm using DoorKeeper to keep the API endpoints secure on all actions other than show.
The problem I'm running into is how best to create that @access_token
.
This works, passing all the examples, however I'm worried I'm not adhering to DRY principles. If lots of actions/contexts require an @access_token
is there a way I could abstract this out somewhere to a helper of sorts?
Thanks in advance
## projects_spec.rb
require 'spec_helper'
describe "Projects API" do
describe "#index" do
FactoryGirl.create(:project)
context 'with a valid token' do
before(:each) do
user = FactoryGirl.create(:user)
authentication = FactoryGirl.create(:authentication, user: user)
application = Doorkeeper::Application.create!(:name => "MyApp", :redirect_uri => "http://app.com")
@access_token = Doorkeeper::AccessToken.create!(:application_id => application.id, :resource_owner_id => authentication.identity.id)
end
it 'returns a list of projects' do
get '/api/v1/projects', access_token: @access_token.token
expect(response.status).to eq(200)
# check the JSON is as we expect
end
end
context 'without a token' do
it 'responds with 401' do
get '/api/v1/projects'
expect(response.status).to eq(401)
end
end
end
describe "#create" do
context 'with a valid token' do
before(:each) do
user = FactoryGirl.create(:user)
authentication = FactoryGirl.create(:authentication, user: user)
application = Doorkeeper::Application.create!(:name => "MyApp", :redirect_uri => "http://app.com")
@access_token = Doorkeeper::AccessToken.create!(:application_id => application.id, :resource_owner_id => authentication.identity.id)
end
context 'with required params' do
project_params = {
name: "Win the lottery",
strapline: "The best feeling in the world"
}
it "creates a project and responds with 201" do
post "/api/v1/projects", :project => project_params, access_token: @access_token.token
expect(response.status).to eq(201)
# check the JSON is as we expect
end
end
context 'without required params' do
project_params = {
strapline: "Stepney City Farm's pallets, woodchips and compost",
}
it "responds with 422 and no record created" do
post "/api/v1/projects", :project => project_params, access_token: @access_token.token
expect(response.status).to eq(422)
json = JSON.parse(response.body)
expect(json['project']['errors'].length).to eq(1)
end
end
end
context 'without a token' do
it 'responds with 401' do
get '/api/v1/projects'
expect(response.status).to eq(401)
end
end
end
describe "#show" do
it 'returns a projects' do
project = FactoryGirl.create(:project, name: "A new project")
get "/api/v1/projects/#{project.id}"
expect(response.status).to eq(200)
json = JSON.parse(response.body)
expect(json['project']['name']).to eq(project.name)
expect(GroupRun.last.name).to eq(project.name)
# check the JSON is as we expect
end
end
end
Upvotes: 2
Views: 891
Reputation: 1551
Building on Matthew's answer, with Doorkeeper, I ended up using the following:
module TokenMacros
def generate_access_token_for(user = nil)
user ||= FactoryGirl.create(:user)
authentication = FactoryGirl.create(:authentication, user: user)
application = Doorkeeper::Application.create!(:name => "MyApp", :redirect_uri => "http://app.com")
access_token = Doorkeeper::AccessToken.create!(:application_id => application.id, :resource_owner_id => authentication.identity.id)
access_token.token
end
end
which then allows me to call...
let(:user) { FactoryGirl.create(:user) }
let(:token) { generate_access_token_for(user) }
splendid
Upvotes: 1
Reputation: 4027
I have a few techniques that I use to deal with this stuff.
The first is to use plain ruby methods over let
. This is just my preference, I think it adds clarity to tests. Check this for more about that: http://robots.thoughtbot.com/lets-not
And then, I have a helper method for auth stuff. I'm using Devise for authentication, so my implementation will be different than yours, but this sets the HTTP_AUTHORIZATION header for every request made to my app after calling the helper method in the tests.
module Requests
module AuthenticationHelpers
def basic_http_auth(user)
credentials = ActionController::HttpAuthentication::Basic.encode_credentials(user.email,user.password)
Rack::MockRequest::DEFAULT_ENV['HTTP_AUTHORIZATION'] = credentials
end
end
end
And then in the actual test, I'll do something like this:
describe "GET /api/v1/messages" do
it 'returns an array of messages' do
get '/api/v1/messages'
basic_http_auth(FactoryGirl.create(:user))
expect(response).to eq(200)
end
end
So, if you're going to be using this across a lot of the API tests, move it into a support helper. And, if you're calling something multiple times in the same file, write a method (or put it in a let
call) to DRY your your tests.
Upvotes: 1
Reputation: 29439
The easiest thing to do to avoid the duplication would be to define the token in the highest level describe so that it's available for all the examples in your spec.
In addition, to avoid any performance issue for the examples that don't depend on it, you can use let
rather than before
to define the token since let
is lazily evaluated.
What you'd have then, is the following:
let(:access_token) do
user = FactoryGirl.create(:user)
authentication = FactoryGirl.create(:authentication, user: user)
application = Doorkeeper::Application.create!(:name => "MyApp", :redirect_uri => "http://app.com")
Doorkeeper::AccessToken.create!(:application_id => application.id, :resource_owner_id => authentication.identity.id)
end
You'd need to also change your references to this token from @access_token
to access_token
, since you're defining a method and not an instance variable.
Upvotes: 0