使用shoulda-callback-matchers测试带条件的after_save回调报错如何解决
问题原因
- shoulda-callback-matchers 的回调条件匹配逻辑为精确匹配回调定义时
if选项的取值本身,当你使用匿名 lambda 作为if条件时,matcher 无法识别 lambda 内部调用的方法,你传入的符号:saved_change_to_rent?和回调里的匿名 lambda 是完全不同的两个对象,自然匹配失败。 - 你最开始写的测试用的
:rent_changed?也不符合回调的实际逻辑:xxx_changed?是回调触发前生效的脏属性方法,after_save阶段要判断字段是否变更,只能用saved_change_to_xxx?,这个点你的回调写法是对的。
解决方案
方案1(推荐):调整回调定义格式,直接传方法名符号
把模型里的回调修改为直接传入方法名作为 if 条件,不需要额外包裹 lambda:
after_save :update_effort_rate, if: :saved_change_to_rent?
对应测试代码调整为:
context 'callbacks' do it { is_expected.to callback(:update_effort_rate).after(:save).if :saved_change_to_rent? } end
这种写法不仅能让测试通过,代码也更简洁易读。
方案2:保留lambda写法,改用功能测试验证触发逻辑
如果因为业务需要必须保留lambda写法,就不要用shoulda的回调存在性 matcher,改为直接验证回调的触发逻辑是否符合预期:
context 'callbacks' do # 把下方的Model替换为你实际的模型类名 let(:model) { Model.create(rent: 1000) } it 'rent变更保存后触发update_effort_rate' do allow(model).to receive(:update_effort_rate) model.update(rent: 2000) expect(model).to have_received(:update_effort_rate) end it 'rent未变更保存后不触发update_effort_rate' do allow(model).to receive(:update_effort_rate) # 把other_field替换为你模型里除了rent之外的其他可写字段 model.update(other_field: '测试内容') expect(model).not_to have_received(:update_effort_rate) end end
内容的提问来源于stack exchange,提问作者Onib33
相关产品推荐
相关产品推荐

