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
相关产品推荐
相关产品推荐

