Скажем, у меня есть:
class Foo
def do_thing(arg_one:, arg_two:)
# ...
end
end
class Bar
def initialize(foo)
@foo = foo
end
def do_delegated_thing
@foo.do_thing(arg_one: "x", arg_two: "y")
end
end
и тест:
describe Bar do
let(:mock_foo) do
foo = double(Foo)
allow(foo).to receive(:do_thing)
foo
end
let(:subject) { described_class.new(mock_foo) }
it "calls Foo with expected arguments" do
subject.do_delegated_thing
expect(mock_foo).to have_received(:do_thing).with(arg_one: "x", arg_two: "y")
end
end
Теперь предположим, что я хочу провести рефакторинг Foo
и изменить имя аргумента:
class Foo
def do_thing(arg_one_hundred:, arg_two:)
# ...
end
end
Тест все равно пройдет, хотя ожидание receive(:do_thing).with(arg_one: "x", arg_two: "y")
теперь недействительно. На мой взгляд, нельзя ожидать, что do_thing
будет вызываться с недопустимыми аргументами. Есть ли разумный способ обойти это, то есть лучший метод API RSpec, который мне следует использовать, чтобы гарантировать, что ожидание действительно законно?
Вам нужен Проверяющий двойник , в данном случае экземпляр_double.
Из документации (выделено автором):
instance_double
— наиболее распространенный тип проверочного двойника. Он принимает имя класса или объект в качестве первого аргумента, а затем проверяет, будут ли любые заглушенные методы присутствовать в экземпляре этого класса. Кроме того, когда он получает сообщения, он проверяет, что предоставленные аргументы поддерживаются сигнатурой метода как с точки зрения арности, так и с точки зрения разрешенных или обязательных аргументов ключевого слова, если таковые имеются.
Например, если вы измените foo = double(Foo)
на foo = instance_double(Foo)
, ваш исходный тест пройдет успешно, а когда вы измените метод на def do_thing(arg_one_hundred:, arg_two:)
, он завершится неудачно.
class Foo
def do_thing(arg_one:, arg_two:); end
end
class Baz
def do_thing(arg_one_hundred:, arg_two:); end
end
class Bar
def initialize
@foo = Foo.new
end
def do_delegated_thing
foo.do_thing(arg_one: "x", arg_two: "y")
end
private
attr_reader :foo
end
describe Bar do
let(:mock_foo) {instance_double(Foo, do_thing: nil)}
let(:mock_baz) {double(Baz, do_thing: nil)}
let(:mock_baz_2) {instance_double(Baz, do_thing: nil)}
let(:subject) { described_class.new }
it "calls Foo with expected arguments" do
allow(subject).to receive(:foo).and_return(mock_foo)
expect(mock_foo).to receive(:do_thing).with(arg_one: "x", arg_two: "y")
subject.do_delegated_thing
end
context 'comparing double and instance_double' do
it "calls double with unexpected arguments" do
allow(subject).to receive(:foo).and_return(mock_baz)
expect(mock_baz).to receive(:do_thing).with(arg_one: "x", arg_two: "y")
subject.do_delegated_thing
end
it "calls instance_double with unexpected arguments" do
allow(subject).to receive(:foo).and_return(mock_baz_2)
expect(mock_baz_2).to receive(:do_thing).with(arg_one: "x", arg_two: "y")
subject.do_delegated_thing
end
end
end
Выход:
Bar
calls Foo with expected arguments
comparing double and instance_double
calls double with unexpected arguments
calls instance_double with unexpected arguments (FAILED - 1)
Failures:
1) Bar comparing double and instance_double calls instance_double with unexpected arguments
Failure/Error: expect(mock_baz_2).to receive(:do_thing).with(arg_one: "x", arg_two: "y")
Missing required keyword arguments: arg_one_hundred
# ./spec.rb:42:in `block (3 levels) in <top (required)>'
# main.rb:22:in `<main>'
Finished in 0.00752 seconds (files took 0.0997 seconds to load)
3 examples, 1 failure
Я также вернулся к своему примеру кода и очистил его в соответствии с вашими указаниями для будущих поколений. :)
Ах, спасибо! И спасибо, что не обратили внимания на мои небрежные ошибки в примере кода. :)