Rspec测试ActiveRecord回调时特定实例方法断言失败如何解决
测试失败的核心原因
测试中通过let!创建的week_1_sr实例,和模型代码中SurveyRequirement.find_by查询返回的survey实例,是内存中两个完全独立的Ruby对象,二者仅对应数据库的同一条记录,实际object_id并不相同。
Rspec的消息期望是绑定在具体内存对象上的,你给week_1_sr.delighted_survey设置的调用监听,无法作用到业务代码中查询出来的新实例的关联对象上,因此测试失败。
而用expect_any_instance_of的测试可以通过,是因为该方法会给DelightedSurvey的所有实例添加调用监听,不管是哪个内存对象触发方法都能被捕获。
调整方案
方案1:Stub查询返回预创建的实例
直接将SurveyRequirement.find_by的返回值Stub为你提前创建的week_1_sr,保证业务代码中拿到的就是设置了期望的对象:
let!(:week_1_sr) { create(:survey_requirement, :week_1_survey) } it "should fire a CSAT survey after week 1" do # 可添加参数匹配让Stub逻辑更严谨 allow(SurveyRequirement).to receive(:find_by).with( position: [1, "*"], type_of_weighin: ["standard", "*"] ).and_return(week_1_sr) expect(week_1_sr.delighted_survey).to receive(:sendSurvey).once.with( client: client, additional_properties: {delay: 3600} ) create(:check_in, client_id: client.id, type_of_weighin: "standard") create(:check_in, client_id: client.id, type_of_weighin: "standard") end
方案2:全局监听+实例身份校验
如果不想Stub查询逻辑,也可以在全局监听的基础上校验调用的实例是否符合预期:
let!(:week_1_sr) { create(:survey_requirement, :week_1_survey) } it "should fire a CSAT survey after week 1" do target_survey_id = week_1_sr.delighted_survey.id expect_any_instance_of(DelightedSurvey).to receive(:sendSurvey).once do |survey, args| # 校验调用的实例是否正确 expect(survey.id).to eq target_survey_id # 也可以同时校验入参正确性 expect(args[:client]).to eq client expect(args[:additional_properties][:delay]).to eq 3600 end create(:check_in, client_id: client.id, type_of_weighin: "standard") create(:check_in, client_id: client.id, type_of_weighin: "standard") end
额外优化建议
回调中获取position时建议对关联做reload,避免刚创建的记录未同步到关联缓存导致计算的索引错误:
position = client&.check_ins&.reload&.reverse&.index(self)
内容的提问来源于stack exchange,提问作者Jeremy Thomas
相关产品推荐
相关产品推荐

