RSpec + Rubocop - 为什么 receive_message_chain 是一种代码味道?

RSpec + Rubocop - why receive_message_chain is a code smell?

我正准备为我的自定义验证器编写规范,它使用此链来检查 ActiveStorage 附加的文件是否为 txt:

return if blob.filename.extension.match?('txt')

通常,我可以用这个调用来存根它:

allow(attached_file).to receive_message_chain(:blob, :byte_size) { file_size }

Rubocop 说这是一种冒犯并指出我的文档:https://www.rubydoc.info/gems/rubocop-rspec/1.7.0/RuboCop/Cop/RSpec/MessageChain

我必须为 blobbyte_size 声明 double 并将它们放在不同的行中,最后是 5 行代码而不是 1 行。我在这里遗漏了什么吗?

为什么要避免存根消息链?

I would have to declare double for blob and byte_size and stub them in separate lines, ending up with 5 lines of code instead of 1.

事实上,这就是重点。那里有这 5 行可能会让您感到有些不安。这可以被认为是 正设计压力 。您的测试设置很复杂,这告诉您要查看实现。使用 #receive_message_chains 让我们对预先公开复杂交互的设计感到满意。

RSpec 的一位作者在 GitHub issue 中解释了其中的一些内容。

我可以做什么?

一个选项是在测试的设置阶段将夹具文件附加到记录:

before do
  file_path = Rails.root.join("spec", "fixtures", "files", "text.txt")

  record.attribute.attach(io: File.open(file_path), filename: "text.txt")
end

这将端到端地测试验证器,没有任何存根。


另一种选择是提取命名方法,然后将其存根。

在你的验证器中:

def allowed_file_extension?
  blob.filename.extension.match?("txt")
end

在你的测试中:

before do
  allow(validator).to receive(:allowed_file_extension?).and_return(true)
end

这有一个额外的好处,即通过命名概念使代码更清晰一些。 (即使您使用测试夹具,也没有什么可以阻止您添加此方法。)

作为对比,我经常通过围绕日志记录的测试遇到这种 rubocop 违规行为,例如:

expect(Rails).to receive_message_chain(:logger, :error).with('limit exceeded by 1')
crank_it_up(max_allowed + 1) 

我可以模拟 Rails 到 return 记录器的替身,然后检查替身是否收到 :error。但这有点愚蠢,IMO。 Rails.logger.error 与其说是消息链,不如说是个成语。

我可以在我的模型或助手中创建一个 log_error 方法(有时我会这样做),但通常这只是 Rails.logger.error

的无意义包装

所以,我要么最终禁用该行的 RSpec/MessageChain,要么可能禁用整个项目(因为我 永远不会 滥用它......对?)如果有一种方法可以在整个项目中对 disabling/muting 这个警察更有选择性……但无论如何我不确定它是如何工作的。