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
我必须为 blob
和 byte_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 这个警察更有选择性……但无论如何我不确定它是如何工作的。
我正准备为我的自定义验证器编写规范,它使用此链来检查 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
我必须为 blob
和 byte_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 这个警察更有选择性……但无论如何我不确定它是如何工作的。