How to do request spec for JWT authenticate app using RSpec

With the help of Lorem's answer, I was able to implement something similar for my request spec. Sharing it here for others to see an alternate implementation.

# spec/requests/locations_spec.rb
require 'rails_helper'

RSpec.describe 'Locations API' do
  let!(:user) { create(:user) }
  let!(:locations) { create_list(:location, 10, user_id: user.id) }

  describe 'GET /locations' do
    it 'reponds with invalid request without JWT' do
      get '/locations'
      expect(response).to have_http_status(401)
      expect(response.body).to match(/Invalid Request/)
    end

    it 'responds with JSON with JWT' do
      jwt = confirm_and_login_user(user)
      get '/locations', headers: { "Authorization" => "Bearer #{jwt}" }
      expect(response).to have_http_status(200)
      expect(json.size).to eq(10)
    end
  end
end

confirm_and_login_user(user) is defined in a request_spec_helper which is included as a module in rails_helper.rb:

# spec/support/request_spec_helper.rb

module RequestSpecHelper
  def json
    JSON.parse(response.body)
  end

  def confirm_and_login_user(user)
    get '/users/confirm', params: {token: user.confirmation_token}
    post '/users/login', params: {email: user.email, password: 'password'}
    return json['auth_token']
  end
end

I'm using the jwt gem for generating my tokens as described in this SitePoint tutorial (https://www.sitepoint.com/introduction-to-using-jwt-in-rails/)


  def authenticated_header(user)
    token = Knock::AuthToken.new(payload: { sub: user.id }).token
    { 'Authorization': "Bearer #{token}" }
  end

  describe 'GET /users?me=true' do
    URL = '/v1/users?me=true'
    AUTH_URL = '/user_token'

    context 'when the request with NO authentication header' do
      it 'should return unauth for retrieve current user info before login' do
        get URL
        expect(response).to have_http_status(:unauthorized)
      end
    end

    context 'when the request contains an authentication header' do
      it 'should return the user info' do
        user  = create(:user)

        get URL, headers: authenticated_header(user)
        puts response.body
      end
    end
  end

Lorem's answer mostly worked for me. I got unrecognized keyword setting headers: on the get. I modified the authenticated_header method and put it in support/api_helper.rb so I could reuse it. The modification is to merge the auth token into request.headers.

# spec/support/api_helper.rb
module ApiHelper
  def authenticated_header(request, user)
    token = Knock::AuthToken.new(payload: { sub: user.id }).token
    request.headers.merge!('Authorization': "Bearer #{token}")
  end
end

In each spec file testing the api, I include api_helper.rb. And I call authenticated_header just before the get statement when testing the case of valid authentication...

# spec/controllers/api/v2/search_controller_spec.rb
RSpec.describe API::V2::SearchController, type: :controller do
  include ApiHelper
...
describe '#search_by_id' do
  context 'with an unauthenticated user' do
    it 'returns unauthorized' do
      get :search_by_id, params: { "id" : "123" }
      expect(response).to be_unauthorized
    end
  end

  context 'with an authenticated user' do
    let(:user) { create(:user) }

    it 'renders json listing resource with id' do
      expected_result = { id: 123, title: 'Resource 123' }
      authenticated_header(request, user)
      get :search_by_id, params: { "id" : "123" }
      expect(response).to be_successful
      expect(JSON.parse(response.body)).to eq expected_result
    end
  end

The key lines in this second test are...

authenticated_header(request, user)
get :search_by_id, params: { "id" : "123" }