You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rspec allow块失效?测试调用HubSpot API的Rails Job遇问题

问题描述

我在测试一个Rails Job,该Job会调用HubSpot的GET /crm/v3/owners/{ownerId} API,并用返回结果更新数据库记录。我使用hubspot-api-ruby gem作为API封装,但测试中的before块似乎未生效——传入的owner_id被判定为假值,导致无法获取Owner对象,测试失败。以下是相关代码及测试结果:

Job代码

module Hubspots
  module Contracts
    class UpdateJob < BaseJob
      queue_as :high_priority

      def perform(attributes)
        contract = Contract.find_by(hubspot_sales_deal_id: attributes[:hubspot_sales_deal_id])
        return if contract.nil?

        deal_owner = client.crm.owners.owners_api.get_by_id(owner_id: attributes[:hubspot_tailor_deal_owner],
                                                            id_property: 'id', archived: false)

        attributes[:hubspot_tailor_deal_owner] = get_owner_name(deal_owner)

        contract.update!(attributes)
      end

      private

      def get_owner_name(hubspot_owner_object)
        "#{hubspot_owner_object.last_name.upcase} #{hubspot_owner_object.first_name.capitalize}"
      end
    end
  end
end

测试代码

RSpec.describe Hubspots::Contracts::UpdateJob, type: :job do
  let!(:job) { described_class.new }
  let(:perform) { job.perform(attributes) }
  let!(:contract) { create(:contract, hubspot_sales_deal_id: 123) }
  let!(:attributes) do
    { hubspot_tailor_deal_id: 456, hubspot_tailor_deal_owner: 876, hubspot_sales_deal_id: 123 }
  end
  let!(:deal_owner_api) { Hubspot::Client.new(access_token: ENV['HUBSPOT_ACCESS_TOKEN']).crm.owners.owners_api }
  let!(:deal_owner_properties) { { last_name: 'Doe', first_name: 'John' } }

  before do
    allow(deal_owner_api).to receive(:get_by_id).and_return(deal_owner_properties)
  end

  describe '#perform' do
    it 'updates contract' do
      expect { perform }.to change { contract.reload.hubspot_tailor_deal_owner }.from(nil)
                                                                                .to('DOE John')
    end
  end
end

测试结果

测试结果截图

问题原因及解决方案

测试mock未生效的核心原因是:测试中手动创建的deal_owner_api实例,与Job里client返回的实例不是同一个对象。Job里的client由BaseJob提供,和你手动初始化的Hubspot::Client.new完全独立,因此你mock的是另一个实例的方法,Job不会调用它。此外,直接返回Hash不符合hubspot-api-ruby的返回结构,会导致get_owner_name方法调用属性时出错。

修改后的测试代码

RSpec.describe Hubspots::Contracts::UpdateJob, type: :job do
  let(:job) { described_class.new }
  let(:perform) { job.perform(attributes) }
  let(:contract) { create(:contract, hubspot_sales_deal_id: 123) }
  let(:attributes) do
    { hubspot_tailor_deal_id: 456, hubspot_tailor_deal_owner: 876, hubspot_sales_deal_id: 123 }
  end
  # 模拟HubSpot客户端及API实例
  let(:mock_hubspot_client) { double('Hubspot::Client') }
  let(:mock_owners_api) { double('Hubspot::Crm::Owners::OwnersApi') }
  # 模拟符合gem返回格式的Owner对象
  let(:mock_deal_owner) do
    double('PublicOwner', last_name: 'Doe', first_name: 'John')
  end

  before do
    # 让Job的client方法返回mock客户端
    allow(job).to receive(:client).and_return(mock_hubspot_client)
    # 关联mock客户端的链式调用到mock API实例
    allow(mock_hubspot_client).to receive_message_chain(:crm, :owners, :owners_api).and_return(mock_owners_api)
    # 精准mock get_by_id方法及返回值
    allow(mock_owners_api).to receive(:get_by_id)
      .with(owner_id: attributes[:hubspot_tailor_deal_owner], id_property: 'id', archived: false)
      .and_return(mock_deal_owner)
  end

  describe '#perform' do
    it 'updates contract' do
      expect { perform }.to change { contract.reload.hubspot_tailor_deal_owner }
        .from(nil).to('DOE John')
    end
  end
end

关键修改说明

  • 用double模拟对象,避免依赖真实HubSpot客户端初始化,无需读取环境变量。
  • 通过receive_message_chain模拟client.crm.owners.owners_api的链式调用,确保Job调用的是mock的API实例。
  • 返回的mock对象提供last_name和first_name属性访问器,与gem实际返回结构一致,保证get_owner_name方法正常工作。

内容的提问来源于stack exchange,提问作者Raphtml

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 06:45:52