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

RSpec测试报错:期望方法调用1次却收到0次,如何针对特定User实例断言?

问题

测试用例返回如下错误:

expected: 1 time with any arguments
received: 0 times with any arguments

明明对应的Active Record确实调用了目标方法,但测试始终不通过。相关代码如下:

测试代码

context "when the City has updated" do 
  let!(:city) { create(:city, ...) }
  let!(:user) { create(:user, city: city) }
  let!(:user2) { create(:user, city: city) }

  it "should call the process_schedule_period for each of the user" do 
    expect(user).to receive(:create_new_period_for_mayor)
    expect(user2).to receive(:create_new_period_for_mayor)
    city.update_attribute(:mayor, "mr alford")
    Delayed::Worker.new.run Delayed::Job.find_by(process_identifier: "process_city_changes")
  end
end

create_new_period_for_mayor方法在延迟任务中被调用,我已经手动运行了该延迟任务。

模型代码(city.rb)

after_save回调

self.delay(process_identifier: "process_city_changes").process_city_data_changes

对应方法实现

def process_city_data_changes
  users = self.users
  users.each do |user|
    user.create_new_period_for_mayor
  end
end

我已经通过调试器确认每个目标用户实例的create_new_period_for_mayor方法都被调用了,但测试始终返回上述错误。用expect_any_instance_of(User)时测试能通过,但我需要针对每个特定的user实例做断言,而且确认调用方法的就是我在测试里创建的那两个实例,请问问题出在哪?

解决方案

问题出在延迟任务序列化实例时,会重新从数据库加载对象,导致测试里的user/user2实例和延迟任务中调用方法的实例不是同一个内存对象。

RSpec的expect(obj).to receive(:method)是针对特定内存实例做的断言,而延迟任务运行时,self.users会从数据库重新查询出User记录,这些是新的内存实例,和测试代码里提前创建的user/user2不是同一个对象,所以断言不会命中。

解决办法有两种:

  • 方法一:在延迟任务执行前,把测试用的实例重新从数据库加载,更新断言的目标对象
    it "should call the process_schedule_period for each of the user" do 
      city.update_attribute(:mayor, "mr alford")
      # 重新加载实例,确保和延迟任务里的实例一致
      user.reload
      user2.reload
      expect(user).to receive(:create_new_period_for_mayor)
      expect(user2).to receive(:create_new_period_for_mayor)
      Delayed::Worker.new.run Delayed::Job.find_by(process_identifier: "process_city_changes")
    end
    
  • 方法二:用实例的唯一标识(比如id)来验证调用,结合and_call_original确保方法正常执行,同时验证调用的是目标实例
    it "should call the process_schedule_period for each of the user" do 
      expect_any_instance_of(User).to receive(:create_new_period_for_mayor) do |instance|
        expect([user.id, user2.id]).to include(instance.id)
      end.and_call_original
      city.update_attribute(:mayor, "mr alford")
      Delayed::Worker.new.run Delayed::Job.find_by(process_identifier: "process_city_changes")
    end
    

内容的提问来源于stack exchange,提问作者jansha

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 16:33:29