Ожидания Ruby rspec: есть ли способ гарантировать, что ожидание ключевого слова не является «фиктивным»?

Скажем, у меня есть:

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, который мне следует использовать, чтобы гарантировать, что ожидание действительно законно?

Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
1
0
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вам нужен Проверяющий двойник , в данном случае экземпляр_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

Ах, спасибо! И спасибо, что не обратили внимания на мои небрежные ошибки в примере кода. :)

aarestad 20.08.2024 19:54

Я также вернулся к своему примеру кода и очистил его в соответствии с вашими указаниями для будущих поколений. :)

aarestad 20.08.2024 22:08

Другие вопросы по теме