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

Rspec测试中after_commit回调内contract.touch未被mock捕获的问题

解决Rails中belongs_to touch的Mock未捕获调用问题

问题原因

你遇到的核心问题是:belongs_to :contract, touch: true触发的touch调用,并不是在你测试中创建的@contract实例上执行的。Rails在处理关联touch时,会重新从数据库加载对应的Contract记录来执行touch方法——也就是说,实际执行touch的是一个新的Contract实例,而不是你预先mock的@contract,所以你的mock没捕获到这个调用,哪怕数据库里的updated_at确实更新了。

解决办法

1. 拦截Contract类的所有实例touch调用

用RSpec的allow_any_instance_of拦截Contract类任意实例的touch方法,不管Rails重新加载哪个实例,都能被捕获:

before do
  @contract = create(:contract)
  allow_any_instance_of(Contract).to receive(:touch)
end

it "triggers contract touch after benefit creation" do
  create(:benefit, contract: @contract)
  expect_any_instance_of(Contract).to have_received(:touch)
end

注:any_instance_of虽有使用争议,但在这类场景下能快速解决问题。

2. 强制Rails使用你mock的Contract实例

拦截Contract.find方法,让它返回你预先创建的@contract,这样Rails重新查询时拿到的还是同一个mock实例:

before do
  @contract = create(:contract)
  allow(@contract).to receive(:touch)
  allow(Contract).to receive(:find).with(@contract.id).and_return(@contract)
end

it "triggers touch on the mocked contract instance" do
  benefit = create(:benefit, contract: @contract)
  expect(@contract).to have_received(:touch)
end

3. 直接验证数据库字段变化

既然已经确认touch实际执行了,也可以跳过mock,直接验证updated_at的变化,这种方式更贴近实际业务行为:

it "updates contract's updated_at after benefit is created" do
  original_updated_at = @contract.updated_at
  create(:benefit, contract: @contract)
  @contract.reload
  expect(@contract.updated_at).to be > original_updated_at
end

补充说明

为什么update_contract的mock能通过?因为你的after_commit回调是直接在你创建的Benefit实例上调用的,这个实例全程是同一个,所以mock能准确捕获到调用。

内容的提问来源于stack exchange,提问作者Bharat Kumar Anand

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 19:47:08