简体   繁体   中英

How to create a current_user for tests? without Devise

my app is setting a current_user for all application, so that was ok until creating the test, in the main controller i am setting the user

class ApplicationController < ActionController::API
  ...

  def authenticate_action

  end

now i was writing the tests:

RSpec.describe 'Api::V1::Articles', type: :request do
  let(:user) {  FactoryBot.build_stubbed :user }
  
    describe 'POST /create' do
      context "with valid user params" do
        let!(:articles_params) { {article:{ name: "art1" } }}
        it 'creates a new article' do
          expect { post "/api/v1/posts/1/articles", params: articles_params }  .to change(Article, :count).by(1)
        end
      end
    end
end

but that user in the test is only fake, thats why i am getting this error:

Completed 400 Unauthorized

It depends on if you want to actually test your authentication too on every request. If that is the case, you need to set a valid auth headers for that user, like this:

RSpec.describe 'Api::V1::Articles', type: :request do
  let(:user) {  FactoryBot.create :user }
  let(:auth_header) { { 'Authorization' => "TOKEN#{user.generate_bearer_token}" } }

  describe 'POST /create' do
    context "with valid user params" do
      let(:articles_params) { {article:{ name: "art1" } }}

      it 'creates a new article' do
        expect { 
          post "/api/v1/posts/1/articles", params: articles_params, headers: auth_header
        }.to change(Article, :count).by(1)
      end
    end
  end
end

Note that you need to change "TOKEN#{user.generate_bearer_token}" in the third line of the above example with an implementation from your application to generate a valid bearer token for the given user.

Or you can decide to not care about how authentication is implemented, and mock the whole authentication logic in the test. Then you have, of course, to test the authentication logic in other places to ensure the implementation actually works in general.

To mock the implementation, I would first move parts of the authenticate_action method into a class method in the user model or a class in the Auth namespace and then just call that one method in authenticate_action :

# in app/models/user.rb
def self.find_by_bearer_token(token)
  id = Auth::TokenValidator.call(token).result
  User.find(id) if id
end

# in your application_controller
def authenticate_action
  @user = User.find_by_bearer_token(bearer_token)

  render json: 400, status: :unauthorized unless @user
end

With such a class method, it is much easier to mock that method in the test and return whatever user you want to use for the spec:

RSpec.describe 'Api::V1::Articles', type: :request do
  let(:user) {  FactoryBot.create :user }
  
  before { allow(User).to receive(:find_by_bearer_token).and_return(user) }

  describe 'POST /create' do
    context "with valid user params" do
      let(:articles_params) { {article:{ name: "art1" } }}

      it 'creates a new article' do
        expect { 
          post "/api/v1/posts/1/articles", params: articles_params
        }.to change(Article, :count).by(1)
      end
    end
  end
end

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM