我刚刚完成第一年在 Rails 中编写生产代码的工作,我并没有真正被困在这里,但我所经历的非 ruby 性很奇怪,我想看看是否有人遇到这个问题。
假设您正在编写涉及外部 API 的代码,因此您对该 API 的调用需要精心设计。为了确保这些调用正确发生,我想编写一些规范,要求这些调用仅发生一定次数并带有特定参数。像这样的东西:
it "only calls the wrapper once with the correct arguments" do
expect(Wrapper).to have_received(:do_magic).once
expect(Wrapper).to have_received(:do_magic).with(flowers_in_sleeve: false)
end
但是有两个期望是不好的做法!我使用 RuboCop 来检查(我喜欢遵守它的约定),所以我有两个选择:要么将其分解为两个期望,要么禁用在一个块中具有多个期望的规则。
所以,这并不是一个完全惊天动地的问题。但我觉得很奇怪,RSpec 没有更方便的方法来满足这种情况。也许我只需要忍住并写下两个期望。我的目标是如果可能的话,在一个期望中做到这一点。
这是我想到的主要解决方案:
it "only calls the wrapper once with the correct arguments" do
expect(Wrapper).to have_received(:do_magic).once.with(flowers_in_sleeve: false)
end
但是,只要您使用这些参数至少调用该方法一次,就允许对包装器进行多次调用。真的,如果你看看你写的句子:
I expect wrapper to be called once with flowers in sleeve to be false
这种行为是有道理的,因为我不是说:
I expect wrapper to be called only once with flower in sleeve to be false
.
正如 @max 评论的那样,有多重期望并不一定是不好的做法。 多个
expect
语句完全有可能有助于覆盖一种行为。当您有令人信服的理由时,您可以自由修改 RuboCop 默认值。
话虽如此,您可以使用以下策略来编写规范,要求调用仅发生一定次数并带有特定参数。您可以捕获传递给每次调用
do_magic
的所有参数,然后针对参数集捕获 expect
。
# lib/magic_performer.rb
class Wrapper
def self.do_magic(options = {})
# implementation goes here
end
end
class MagicPerformer
def perform_magic
Wrapper.do_magic(flowers_in_sleeve: false)
end
end
# spec/magic_performer_spec.rb
require 'rspec'
require_relative '../lib/magic_performer'
RSpec.describe MagicPerformer do
let(:args_in_calls_to_do_magic) { [] }
before do
allow(Wrapper).to receive(:do_magic) do |*args, **kwargs|
args_in_calls_to_do_magic << [args, kwargs]
end
MagicPerformer.new.perform_magic
end
it 'only calls the wrapper once with the correct arguments' do
expect(args_in_calls_to_do_magic)
.to eq([
[[], { flowers_in_sleeve: false }]
])
end
end
另外,考虑这种类型的测试是“白盒”测试(与“黑盒”测试相对)。这样的测试了解被测类
Wrapper
的实现细节。一般来说,最好编写一个不知道任何此类实现细节的测试,例如Wrapper
内部调用MagicPerformer
。黑盒测试使您能够在将来重构被测类,例如删除其对 MagicPerformer
的依赖,而无需更改测试。考虑是否可以重写测试,使其仅针对 Wrapper
的可观察行为进行预期。