Издевательство / удаление метода, включенного из «instance.extend (DecoratorModule)»

#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 не удается записать взаимодействие с этим объектом.