Как проверить, что метод вызван

Имею следующее:

class Foo
  def bar(some_arg)
  end
end

Он называется Foo.new.bar(some_arg). Как мне проверить это в rspec? Я не знаю, как узнать, создал ли я экземпляр Foo с именем bar.

Не могли бы вы добавить дополнительные детали к вопросу. Согласно моему ответу, этот тип издевательства - это запах кода / теста.

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

Ответы 3

Если вы издеваетесь над этими методами в другой спецификации класса (скажем, BazClass), то фиктивный метод просто вернет объект с информацией, которую вы ожидаете. Например, если вы используете Foo # bar в этой спецификации Baz # some_method, вы можете сделать это:

# Baz#some_method
def some_method(some_arg)
  Foo.new.bar(some_arg)
end

#spec for Baz
it "baz#some_method" do
  allow(Foo).to receive_message_chain(:bar).and_return(some_object)
  expect(Baz.new.some_method(args)).to eq(something) 
end

в противном случае, если вы хотите, чтобы Foo действительно вызывал метод и запускал его, вы бы просто регулярно вызывали метод

#spec for Baz
it "baz#some_method" do
  result = Baz.new.some_method(args)
  @foo = Foo.new.bar(args)
  expect(result).to eq(@foo) 
end

редактировать:

it "Foo to receive :bar" do
  expect(Foo.new).to receive(:bar)
  Baz.new.some_method(args)
end

Что делать, если Foo.new.bar ничего не возвращает? Я не хочу издеваться над возвращаемым объектом только для того, чтобы знать, что он был вызван. Обратите внимание, что метод панели тщательно протестирован в спецификации Foo, и я не хочу повторять тесты.

Terence Chow 14.12.2018 05:08

Может как то так? Потребуется дополнительная информация, что "Foo to receive: bar" действительно ожидает (Foo.new). To receive (: bar) Baz.new.some_method (args) end

Andy 14.12.2018 05:24

Самый простой способ - использовать expect_any_instance_of.

expect_any_instance_of(Foo).to receive(:bar).with(expect_arg).and_return(expected_result)

Тем не менее, этот метод не рекомендуется, поскольку он сложен, вызывает неприятный запах дизайна и может привести к странному поведению. Предлагаемое использование предназначено для устаревшего кода, над которым у вас нет полного контроля.


Рассуждая о том, как выглядит ваш код, я бы ожидал чего-то вроде этого:

class Baz
  def do_stuff
    Foo.new.bar(arg)
  end
end

it 'tests Baz but have to use expect_any_instance_of' do
  expect_any_instance_of(Foo).to receive(:bar).with(expect_arg).and_return(expected_result)
  Baz.do_stuff
  # ...
end

Если вы оказались в такой ситуации, лучше всего поднять экземпляр класса до аргумента по умолчанию, например:

class Baz
  def do_stuff(foo_instance = Foo.new)
    foo_instance.bar(arg)
  end
end

Таким образом, вы можете передать макет вместо экземпляра по умолчанию:

it 'tests Baz properly now' do
  mock_foo = stub(Foo)
  Baz.do_stuff(mock_foo)

  # ...
end

Это известно как внедрение зависимостей. Это немного похоже на забытое искусство в Ruby, но если вы прочитаете о шаблонах тестирования Java, вы найдете его. Кроличья нора уходит довольно глубоко, когда вы начинаете идти по этому маршруту, и Руби, как правило, становится излишним.

Если это запах дизайна, то как правильно?

Terence Chow 14.12.2018 05:10

@TerenceChow недостаточно подробностей в вашем вопросе, чтобы дать вам лучший ответ.

Gavin Miller 14.12.2018 05:11

Представьте, что вызывающей стороне нужно вызвать foo.new.bar, потому что это почти похоже на транзакцию. Foo.new.bar уже был протестирован на собственном модульном тесте. Итак, как мне убедиться, что метод вызывающего абонента выполняет эту транзакцию? Легко проверить, что вызывающий абонент делает что-то за пределами foo.new.bar, но не уверен, как проверить, что вызывается foo.new.bar

Terence Chow 14.12.2018 05:20
Ответ принят как подходящий

receive_message_chain считается запахом, поскольку он позволяет легко нарушить Закон Деметры.

expect_any_instance_of считается запахом, поскольку он не определяет, какой экземпляр Foo вызывается.

Как отметил @GavinMiller, эти методы обычно предназначены для устаревшего кода, который вы не контролируете.

Вот как протестировать Foo.new.bar(arg) без них:

class Baz
  def do_something
    Foo.new.bar('arg')
  end
end

describe Baz do
  subject(:baz) { described_class.new }

  describe '#do_something' do
    let(:foo) { instance_double(Foo, bar: true) }

    before do
      allow(Foo).to receive(:new).and_return(foo)

      baz.do_something
    end

    it 'instantiates a Foo' do
      expect(Foo).to have_received(:new).with(no_args)
    end

    it 'delegates to bar' do
      expect(foo).to have_received(:bar).with('arg')
    end
  end
end

Примечание. Для простоты я жестко кодирую аргумент. Но вы также можете легко и посмеяться над этим. Отображение этого здесь будет зависеть от того, как создается экземпляр arg.

РЕДАКТИРОВАТЬ

Важно отметить, что эти тесты хорошо знакомы с базовой реализацией. Следовательно, если вы измените реализацию, тесты не пройдут. Как решить эту проблему, зависит от того, что именно делает метод Baz#do_something.

Допустим, Baz#do_something на самом деле просто ищет значение из Foo#bar на основе arg и возвращает его, нигде не меняя состояния. (Это называется методом Query.) В этом случае наши тесты вообще не должны заботиться о Foo, они должны заботиться только о том, чтобы правильное значение было возвращено Baz#do_something.

С другой стороны, предположим, что Baz#do_something действительно где-то меняет состояние, но не возвращает проверяемое значение. (Это называется командным методом.) В этом случае нам нужно подтвердить, что правильные участники были вызваны с правильными параметрами. Но мы можем быть уверены, что модульные тесты для этих других объектов действительно будут проверять их внутреннюю структуру, поэтому мы можем использовать имитацию в качестве заполнителей. (Тесты, которые я показал выше, относятся к этому разнообразию.)

Есть фантастический говорить на это Сэнди Мец еще в 2013 году. Специфика технологий, которые она упоминает, изменилась. Но основное содержание - как проверить то, что сегодня на 100% актуально.

На мой взгляд это лучший ответ

oren 14.12.2018 12:31

Кажется, это именно то, что я ищу. Можете ли вы поделиться, если есть какие-то ошибки, или это рекомендуемый способ делать что-то?

Terence Chow 14.12.2018 16:15

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