#ruby-on-rails #ruby #testing #mocking #ruby-mocha
#ruby-on-rails #ruby #тестирование #издевательство #ruby-мокко
Вопрос:
Я использую модуль декоратора, который включается в экземпляр модели (с помощью метода «extends»). Так, например :
module Decorator
def foo
end
end
class Model < ActiveRecord::Base
end
class ModelsController < ApplicationController
def bar
@model = Model.find(params[:id])
@model.extend(Decorator)
@model.foo
end
end
Затем я хотел бы в тестах выполнить следующее (используя Mocha) :
test "bar" do
Model.any_instance.expects(:foo).returns("bar")
get :bar
end
Возможно ли это каким-то образом, или вы имеете в виду какой-либо другой способ получить эту функциональность???
Ответ №1:
Просто предположение: я предполагаю, что ваш метод Decorator foo возвращает «bar», который не показан в отправленном вами коде. Если я не предполагаю этого, то ожидания в любом случае не будут выполнены, потому что метод возвращает nil, а не «bar».
Предполагая, как указано выше, я попробовал всю историю, как у вас, с совершенно новым приложением rails, и я понял, что это невозможно сделать. Это связано с тем, что метод foo не привязан к модели класса, когда в вашем тесте вызывается метод expects.
Я пришел к такому выводу, пытаясь следовать стеку вызываемых методов во время ожидания. ожидает, что вызовет заглушки в Mocha::Central, который вызывает заглушки в Mocha::ClassMethod, который вызывает *hide_original_method * в Mocha::AnyInstanceMethod . Там *hide_original_method * не находит никакого метода для скрытия и ничего не делает. Затем моделируйте.метод foo не является псевдонимом для заглушенного метода mocha, который должен быть вызван для реализации вашего ожидания mocha, но фактической модели.вызывается метод foo, который вы динамически подключаете к экземпляру модели внутри вашего контроллера.
Мой ответ заключается в том, что это невозможно сделать.
Комментарии:
1. Это полезно, и на самом деле это источник проблемы. Я хотел бы спросить разработчиков Mocha, есть ли способ сделать это на самом деле. Из соответствующего обсуждения они явно заявили, что использование методов насмешливого модуля невозможно. Только классовые … 🙁
Ответ №2:
Это работает (подтверждено в тестовом приложении с помощью render:text)
Я обычно включаю декораторы (вместо того, чтобы расширять их во время выполнения), и я избегаю any_instance, поскольку это считается плохой практикой (вместо этого я имитирую find).
module Decorators
module Test
def foo
"foo"
end
end
end
class MoufesController < ApplicationController
def bar
@moufa = Moufa.first
@moufa.extend(Decorators::Test)
render :text => @moufa.foo
end
end
require 'test_helper'
class MoufesControllerTest < ActionController::TestCase
# Replace this with your real tests.
test "bar" do
m = Moufa.first
Moufa.expects(:find).returns(m)
m.expects(:foo).returns("foobar")
get :bar, {:id => 32}
assert_equal @response.body, "foobar"
end
end
Комментарии:
1. Возможно, вы правы в отношении включения и расширения, но в некоторых случаях вы можете захотеть расширять не все экземпляры класса, а только определенный. Какую версию mocha вы используете, и она работает? На моей машине произошел сбой (ошибка Mocha)
2. Ну, читая ответы выше, я думаю, что проблема в any_instance (я вообще не использую any_instance, его даже не существует в RSpec)
3. Да, удаление поиска имеет смысл. Это на один уровень больше с точки зрения издевательства / заглушения, что не очень хорошо, но это работает, и это лучше, чем мой обходной путь. Спасибо.
Ответ №3:
Хорошо, теперь я понимаю. Вы хотите отключить вызов внешней службы. Интересно, что mocha не работает с extend таким образом. Помимо того, что упомянуто выше, похоже, это связано с тем, что заглушенные методы определены в одноэлементном классе, а не в модуле, так что не вмешивайтесь.
Почему бы не что-то подобное?
test "bar" do
Decorator = Module.new{ def foo; 'foo'; end }
get :bar
end
Если вы предпочитаете не получать предупреждения о том, что декоратор уже определен — что является намеком на то, что в любом случае происходит какая-то связь — вы можете ввести его:
class ModelsController < ApplicationController
class << self
attr_writer :decorator_class
def decorator_class; @decorator_class ||= Decorator; end
end
def bar
@model = Model.find(params[:id])
@model.extend(self.class.decorator_class)
@model.foo
end
end
что делает тест похожим:
test "bar" do
dummy = Module.new{ def foo; 'foo'; end }
ModelsController.decorator_class = dummy
get :bar
end
Конечно, если у вас более сложная ситуация с несколькими декораторами или декораторами, определяющими несколько методов, это может не сработать для вас.
Но я думаю, что это лучше, чем заглушить поиск. Обычно вы не хотите заглушать свои модели в интеграционном тесте.
Комментарии:
1. Спасибо Эрику за ответ. На самом деле это то, что я уже делаю (почти). По сути, это и ответ бандито — два варианта, которые действительно работают.
Ответ №4:
Одно незначительное изменение, если вы хотите проверить возвращаемое значение:bar —
test "bar" do
Model.any_instance.expects(:foo).returns("bar")
assert_equal "bar", get(:bar)
end
Но если вы просто проверяете, что экземпляр модели имеет методы декоратора, вам действительно нужно это проверять? Похоже, в этом случае вы тестируете Object#extend .
Если вы хотите протестировать поведение @model.foo, вам не нужно делать это в интеграционном тесте — в этом преимущество декоратора, затем вы можете протестировать его изолированно, например
x = Object.new.extend(Decorator)
#.... assert something about x.foo ...
По моему опыту, издевательство в интеграционных тестах обычно пахнет кодом.
Комментарии:
1. Нет, я не хочу проверять возвращаемое значение действия контроллера (и, следовательно, метод). Я просто хочу издеваться над расширенным методом и, таким образом, «вырезать» реальное действие (которое является внешним вызовом) для тестового объекта. Mocha не удается записать взаимодействие с этим объектом.