RSpec: 如何链接 receive().with()?

RSpec: how to chain receive().with()?

我一直在用 instance_doubles 编写测试,当我需要链中的更多粒度时代表消息链。但是,我想知道我是不是在用艰难的方式做事。

这是我要测试的方法:

def run_produceable_job
  # Delete any jobs that exist, but haven't started, in favor of this new job
  Delayed::Job.where(queue: 'produceable', locked_at: nil).delete_all

  ProduceableJob.perform_later
end

对于 Delayed::Job 调用,重要的是我检查队列名称是否符合预期。 我还想确保 Delayed::Job 在最后收到 .delete_all

我想做这样的事情:

expect(Delayed::Job).to receive(:where).with(queue: 'produceable', locked_at: nil).and_then_receive(:delete_all)
                                                                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

RSpec 是否提供某种接收链接? 我浏览了文档,但找不到任何专门讨论添加多个接收的内容。

还是我必须走很长的路?

ar_relation = instance_double ActiveRecord::Relation
allow(Delayed::Job).to receive(:where).with(queue: 'produceable', locked_at: nil).and_return(ar_relation)
allow(ar_relation).to receive(:delete_all)

expect(Delayed::Job).to receive(:where).with(queue: 'produceable', locked_at: nil)
expect(ar_relation).to receive(:delete_all)

恕我直言,你必须走很长的路。没有更短的方式来描述它。

无论如何,我建议您考虑一下您的测试策略。目前,您正在测试是否调用了非常具体的方法组合,但如果这些方法调用实际上正在执行您希望它们执行的操作,则不会。

相反,我会创建一个应该删除的示例记录(也许还有一对不应该删除的记录),然后 运行 作业,然后测试是否只删除了预期的记录。

例如像这样:

let!(:record_to_be_deleted) { 
  Delayed::Job.create!(queue: 'produceable', locked_at: nil) 
}
let!(:records_to_stay) do
  [ 
    Delayed::Job.create!(queue: 'produceable', locked_at: Time.current),
    Delayed::Job.create!(queue: 'default', locked_at: nil)
  ]
end

it "should remove only expected records" do
  expect {
    instance.run_produceable_job
  }.to chance { Delayed::Job.count }.from(3).to(2)

  expect { 
    record_to_be_deleted.reload
  }.to raise_error(ActiveRecord::RecordNotFound)
end

经验法则是测试预期的结果,而不是具体的实施。因为实现可能会改变,将在未来的版本中重构或可能会中断。