【问题标题】:NoMethodError: undefined method `access_token' for nil:NilClass in RSpecNoMethodError:RSpec中nil:NilClass的未定义方法`access_token'
【发布时间】:2021-07-14 15:12:31
【问题描述】:

当用户将验证码发送到 Github/Google 服务以使用 Octokit gem 接收有效令牌时,我正在尝试通过用户身份验证测试。我正在为 RSpec 中的 nil:NilClass 运行 NoMethodError 未定义方法 `access_token'。

这是我的终端截图:

RSpeca terminal screenshot of the problem 中 nil:NilClass 的未定义方法 `access_token'

这是我的 UserAuthenticator 库:

class UserAuthenticator
  class AuthenticationError < StandardError; end

  attr_reader :user, :access_token

  def initialize(code)
    @code = code
  end

  def perform
    raise AuthenticationError if code.blank?
    raise AuthenticationError if token.try(:error).present?

    prepare_user
    @access_token = if user.access_token.present?
      user.access_token
     else
      user.create_access_token
    end
  end

  # ----------------------------------------------------------------------------
  private


  def client
    @client ||= Octokit::Client.new(
      client_id: ENV['GITHUB_CLIENT_ID'],
      client_secret: ENV['GITHUB_CLIENT_SECRET']
    )
  end

  def token
    @token ||= client.exchange_code_for_token(code)
  end

  def user_data
    @user_data ||= Octokit::Client.new(
      access_token: token
    ).user.to_h.slice(:login, :url, :avatar_url, :name)
  end

  def prepare_user
    if User.exists?(login: user_data[:login])
      @user = User.find_by(login: user_data[:login])
    else
      User.create(user_data.merge(provider: 'github'))
    end
  end

  attr_reader :code
end

这是我用于用户身份验证的 RSpec 文件:

require 'rails_helper'

RSpec.describe UserAuthenticator do
  describe '#perform' do
    let(:authenticator) { described_class.new('sample code') }

    subject { authenticator.perform }

    context 'then the code is invalid' do
      let(:error) {
        double("Sawyer::Resource", error:'bad_verification_code')
      }
      before do
        allow_any_instance_of(Octokit::Client).to receive(
          :exchange_code_for_token).and_return(error)
      end
      it "should raise an error" do
        expect{subject}.to raise_error(UserAuthenticator::AuthenticationError)
        expect(authenticator.user).to be_nil
      end
    end

    context 'when code is correct' do
      let(:user_data) do
        {
          login: 'nklobuc1',
          url: 'http://example.com',
          avatar_url: 'http://example.com/avatar',
          name: 'Nikola'
        }
      end

      before do
        allow_any_instance_of(Octokit::Client).to receive(
          :exchange_code_for_token).and_return('validaccestoken')

          allow_any_instance_of(Octokit::Client).to receive(
            :user).and_return(user_data)
      end

      it "should save the user when does not exist" do
        expect{subject}.to change{ User.count }.by(1)
        expect(User.last.name).to eq('Nikola')
      end

      it "should reuse already registered user" do
        user = create :user, user_data
        expect{subject}.not_to change{User.count}
        expect(authenticator.user).to eq(user)
      end

      it "should create and set user's access token" do
        pp subject
        expect{subject}.to change{AccessToken.count}.by(1)
        expect(authenticator.access_token).to be_present
      end
    end
  end
end

这是用户模型:

class User < ApplicationRecord
  validates :login, presence: true, uniqueness: true
  validates :provider, presence: true

  has_one :access_token, dependent: :destroy
end

最后,这是 AccessToken 模型:

class AccessToken < ApplicationRecord
  belongs_to :user, class_name: "User", :foreign_key => :user_id
  validates_uniqueness_of :user_id
  after_initialize :generate_token

  private

  def generate_token
    loop do
      break if token.present? && !AccessToken.exists?(token: token)
      self.token = SecureRandom.hex(10)
    end

  end
end

【问题讨论】:

    标签: ruby-on-rails ruby rspec octokit


    【解决方案1】:

    我找到了答案。 prepare_user 在我测试时返回了nil

          it "should save the user when does not exist" do
            expect{subject}.to change{ User.count }.by(1)
            expect(User.last.name).to eq('Nikola')
          end
    

    prepare_user 只需要进行以下调整(else 下的另一个实例变量):

    def prepare_user
        if User.exists?(login: user_data[:login])
          @user = User.find_by(login: user_data[:login])
        else
          @user = User.create(user_data.merge(provider: 'github'))
        end
      end
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-01-25
      • 1970-01-01
      • 2012-05-13
      • 2013-07-16
      • 2017-01-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多