【问题标题】:Achieving 100% test coverage in Rails Oauth using Rspec stubs and mocks使用 Rspec 存根和模拟在 Rails Oauth 中实现 100% 的测试覆盖率
【发布时间】:2016-06-15 23:18:27
【问题描述】:

我正在尝试找出一种方法来存根/模拟访问令牌调用,以便为用户令牌过期时调用的方法提供覆盖。我在这个问题上阅读的指南越多,我就越感到困惑。我不想打电话给外部提供者,我想确认方法报告 100% 覆盖率,以防开发人员修改它们并且它们工作不正确。我应该在下面的规范中添加什么以使其达到我们 100% 的测试目标?

load_json_fixture('omitted_oauth') 会根据初始 Oauth 调用返回的内容引入 JSON 固定装置。

模型关注

module OmittedOmniAuthentication
  extend ActiveSupport::Concern

  module ClassMethods
    def from_omniauth(auth)
      Rails.logger.debug auth.inspect
      where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
        setup_user(user, auth)
      end
    end

    def setup_user(user, auth)
      user.provider = auth.provider
      user.uid = auth.uid
      user.email = auth.info.email
      user.customer_ids = auth.extra.raw_info.customer_ids
      user.store_token(auth.credentials)
    end
  end

  def refresh_token!
    access_token ? refresh_access_token! : false
  end

  def refresh_access_token!
    result = access_token.refresh!
    store_token(result)
    save
  rescue OAuth2::Error
    false
  end

  def settings
    @settings ||= Devise.omniauth_configs[:omitted].strategy
  end

  def strategy
    @strategy ||= OmniAuth::Strategies::Omitted.new(nil, settings.client_id, settings.client_secret, client_options: settings.client_options)
  end

  def client
    @client ||= strategy.client
  end

  def access_token
    OAuth2::AccessToken.new(client, token, refresh_token: refresh_token)
  end

  def store_token(auth_token)
    self.token = auth_token.token
    self.refresh_token = auth_token.refresh_token
    self.token_expires_at = Time.at(auth_token.expires_at).to_datetime
  end

  def token_expired?
    Time.now > token_expires_at
  end
end

Rspec 规范

RSpec.describe 'OmittedOmniAuthentication', type: :concern do
  let(:klass) { User }
  let(:user) { create(:user) }
  let(:user_oauth_json_response) do
    unfiltered_oauth_packet = load_json_fixture('omitted_oauth')
    unfiltered_oauth_packet['provider'] = unfiltered_oauth_packet['provider'].to_sym
    unfiltered_oauth_packet['uid'] = unfiltered_oauth_packet['uid'].to_i
    unfiltered_oauth_packet
  end

  before do
    OmniAuth.config.test_mode = true
    OmniAuth.config.mock_auth[:omitted] = OmniAuth::AuthHash.new(
      user_oauth_json_response,
      credentials: { token: ENV['OMITTED_CLIENT_ID'], secret: ENV['OMITTED_CLIENT_SECRET'] }
    )
  end

  describe "#from_omniauth" do
    let(:omitted_oauth){ OmniAuth.config.mock_auth[:omitted] }

    it 'returns varying oauth related data for Bigcartel OAuth response' do
      data = klass.from_omniauth(omitted_oauth)
      expect(data[:provider]).to eq(user_oauth_json_response['provider'].to_s)
      expect(data[:uid]).to eq(user_oauth_json_response['uid'].to_s)
      expect(data[:email]).to eq(user_oauth_json_response['info']['email'])
      expect(data[:customer_ids]).to eq(user_oauth_json_response['extra']['raw_info']['customer_ids'])
    end
  end

  describe '#token expired?' do
    it 'true if valid' do
      expect(user.token_expired?).to be_falsey
    end

    it 'false if expired' do
      user.token_expires_at = 10.days.ago
      expect(user.token_expired?).to be_truthy
    end
  end
end

更新

  describe '#refresh_access_token!' do
    it 'false if OAuth2 Fails' do
      allow(user).to receive(:result).and_raise(OAuth2::Error)
      expect(user.refresh_access_token!).to be_falsey
    end

    it 'false if refresh fails' do
      allow(user).to receive(:access_token) { true }
      allow(user).to receive(:refresh_access_token!) { false }
      expect(user.refresh_token!).to be_falsey
    end

    it 'true if new token' do
      allow(user).to receive(:access_token) { true }
      allow(user).to receive(:refresh_access_token!) { true }
      expect(user.refresh_token!).to be_truthy
    end

    it 'true when refreshed' do
      allow(user).to receive(:access_token) { true }
      allow(user).to receive(:refresh_access_token!) { true }
      allow(user).to receive(:store_token) { true }
      allow(user).to receive(:save) { true }
      expect(user.refresh_access_token!).to be_truthy
    end
  end

=> 通过这些更新,我能够达到 94.12%

【问题讨论】:

  • 这对我来说有点太大太宽泛,无法阅读和理解。你能指出你想要覆盖的一个或多个特定的行吗?
  • @DaveSchweisguth 我已经更新了覆盖问题的问题。
  • 那么,你的图像有你的测试覆盖率吗? token_expired? 方法是否被覆盖?换句话说,Time.now > token_expires_at 是绿色的吗?它在您的图像中不可见。
  • 我对该方法有测试覆盖率,但其他方法不是@sealocal
  • @sealocal 从我的研究中我必须存根 |模拟,我正在拔头发

标签: ruby-on-rails ruby rspec omniauth


【解决方案1】:

我不确定您可能会在哪里调用外部提供程序,所以我不确定您要存根/模拟什么。

为了让您更接近您的覆盖目标,请尝试为您最简单的模块方法添加另一个规范:

  describe '#refresh_token!' do
    it 'is true if there is an access_token' do
      if !user.access_token?
        expect(user.refresh_token!).to be_truthy
      end
    end

    # Do you have factories or fixtures set up that can force
    # #access_token? to be falsey?
    it 'is false if there is no access_token' do
      if !user.access_token?
        expect(user.refresh_token!).to be_falsey
      end
    end

    # Maybe you want to set the falsey value for the access_token
    # as you have have for the value of token_expires_at in
    # your #token_expired? test.
    it 'is false if there is no access_token' do
      # You should be able to force the method to return a false
      # value (stub the method) with this line
      allow(user).to receive(:access_token) { false }
      expect(user.refresh_token!).to be_falsey
    end
  end

这个例子感觉有点不必要,因为你的access_token 方法似乎永远不会返回false。我希望您的 access_token 方法将始终返回一个对象或错误,因此您的 refresh_token! 方法将永远不会在三元中遇到虚假条件。也许你应该救援并返回 false。

无论如何,我认为关键是您应该使用allow 方法对方法进行存根,这样您就可以找出方法存根。希望对您有所帮助。

对于refresh_access_token!,您可以通过将user.result 方法与错误存根,而不是对refresh_access_token! 方法的“成功”结果存根来对该方法进行单元测试。

  describe '#refresh_access_token!' do
    it 'it returns true when refreshed' do
      # The successful control flow path for this method
      # is to save the user and return true.
      # I suppose this would happen smoothly in your tests and app.
      expect(user.refresh_access_token!).to be_truthy
    end

    it 'returns false when an OAuth2 Error is rescued' do
      # To force the case that you receive an OAuth2 Error,
      # stub the user's access_token return value with the Error
      # The refresh_access_token! method should then rescue the error
      # and cover the false return value of the method
      allow(user).to receive(:access_token) { OAuth2::Error }
      expect(user.refresh_access_token!).to be_falsey
    end
  end

【讨论】:

  • 我正处于最后阶段 + 我刚刚做了一些重大修改。想达到 100% 吗?
  • 看起来对于每个调用refresh_access_token! 的测试,您要么存根该方法,要么存根user.result,因此refresh_access_token! 方法永远不会完全执行。您可以为refresh_access_token! 编写一个描述块。也许对于 describe 块中的一个测试,您可以测试成功的条件,而对于另一个,您可以将结果作为您正在救援的错误存根。
  • 好吧,也许我在这里迷路了@sealocal,我该怎么写你在说什么?
  • 我添加了用于单元测试refresh_access_token! 方法的代码示例。
  • 我试过你的例子,但仍然没有达到 100%。我在上面的“更新”下也将我的示例更新为最新的。当我尝试 expect(user.refresh_access_token!).to be_truthy 它失败了 b/c 方法返回 false?我仍然在破解这个。想法?
【解决方案2】:

(代表问题作者发布解决方案)

这正在工作。通过对方法链进行以下规范调整,我能够成功调用该方法的true

  def refresh_access_token!
    result = access_token.refresh!
    store_token(result)
    save
  rescue OAuth2::Error
    false
  end

将我推向 100% 的完整规范

it 'true when refreshed' do
  auth_token = OpenStruct.new(token: FFaker::Lorem.characters(50),
                              refresh_token: FFaker::Lorem.characters(50),
                              expires_at: 5.days.from_now)
  allow(user).to receive_message_chain('access_token.refresh!') { auth_token }
  expect(user.refresh_access_token!).to be_truthy
end

存根和模拟可以很有趣。我从这个线程中学到了很多东西。这是Rspec 3.4 docs on this

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-01-22
    • 1970-01-01
    • 1970-01-01
    • 2013-07-05
    • 2013-01-10
    • 2017-11-17
    • 2013-08-04
    • 2021-01-07
    相关资源
    最近更新 更多