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

