Как проверить, что метод вызывается - PullRequest
0 голосов
/ 14 декабря 2018

У меня есть следующее:

class Foo
  def bar(some_arg)
  end
end

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

Ответы [ 3 ]

0 голосов
/ 14 декабря 2018

Самый простой способ - это использовать 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, вы найдете его.Кроличья нора проходит довольно глубоко, хотя, как только вы начинаете идти по этому пути, она становится излишней для Руби.

0 голосов
/ 14 декабря 2018

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.

EDIT

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

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

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

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

0 голосов
/ 14 декабря 2018

Если вы издеваетесь над этими методами в другой спецификации класса (скажем, BazClass), то метод mock просто вернет объект с ожидаемой вами информацией.Например, если вы используете 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...