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

Если вы издеваетесь над этими методами в другой спецификации класса (скажем, 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, и я не хочу повторять тесты.
Может как то так? Потребуется дополнительная информация, что "Foo to receive: bar" действительно ожидает (Foo.new). To receive (: bar) Baz.new.some_method (args) end
Самый простой способ - использовать 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, вы найдете его. Кроличья нора уходит довольно глубоко, когда вы начинаете идти по этому маршруту, и Руби, как правило, становится излишним.
Если это запах дизайна, то как правильно?
@TerenceChow недостаточно подробностей в вашем вопросе, чтобы дать вам лучший ответ.
Представьте, что вызывающей стороне нужно вызвать foo.new.bar, потому что это почти похоже на транзакцию. Foo.new.bar уже был протестирован на собственном модульном тесте. Итак, как мне убедиться, что метод вызывающего абонента выполняет эту транзакцию? Легко проверить, что вызывающий абонент делает что-то за пределами foo.new.bar, но не уверен, как проверить, что вызывается foo.new.bar
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% актуально.
На мой взгляд это лучший ответ
Кажется, это именно то, что я ищу. Можете ли вы поделиться, если есть какие-то ошибки, или это рекомендуемый способ делать что-то?
Не могли бы вы добавить дополнительные детали к вопросу. Согласно моему ответу, этот тип издевательства - это запах кода / теста.