【问题标题】:Mocking/stubbing a method that's included from "instance.extend(DecoratorModule)"模拟/存根包含在“instance.extend(DecoratorModule)”中的方法
【发布时间】:2011-10-09 14:01:44
【问题描述】:

我使用包含在模型实例中的装饰器模块(通过“扩展”方法)。例如:

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 

这有可能以某种方式实现吗,或者您是否有其他方法来获得此功能???

【问题讨论】:

    标签: ruby-on-rails ruby testing mocking ruby-mocha


    【解决方案1】:

    只是一个假设 注意:我将假设您的 Decorator foo 方法返回“bar”,这未在您发送的代码中显示。如果我不假设这一点,那么期望无论如何都会失败,因为该方法返回 nil 而不是“bar”。

    假设如上所述,我已经用一个全新的 Rails 应用程序尝试了整个故事,我意识到这是不可能的。这是因为在您的测试中调用 expects 方法时,方法 'foo' 没有附加 到类 Model

    我在 expects 中尝试跟踪调用的方法堆栈得出了这个结论。 expects调用Mocha::Central中的stubs,后者调用Mocha::ClassMethod中的stubs em>,它在 Mocha::AnyInstanceMethod 中调用 *hide_original_method*。在那里,*hide_original_method* 没有找到任何隐藏的方法并且什么也不做。然后 Model.foo 方法没有别名为 stubbed mocha 方法,应该调用它来实现您的 mocha 期望,但是调用实际的 Model.foo 方法,即您可以动态附加到控制器内的模型实例。

    我的回答是做不到。

    【讨论】:

    • 这很有帮助,实际上这是问题的根源。我想问问 Mocha 开发人员是否有办法真正做到这一点。从相关讨论中他们明确表示,尽管模拟模块方法是不可能的。只有类... :(
    【解决方案2】:

    它可以工作(在带有渲染的测试应用程序中确认:文本)

    我通常包含装饰器(而不是在运行时扩展它们)并且我避免使用 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
    

    【讨论】:

    • 您可能对 include 与 extend 的看法是对的,但在某些情况下,您可能不想扩展一个类的所有实例,而只扩展一个特定的实例。您使用的是哪个 mocha 版本并且可以使用?它在我的机器上失败(摩卡错误)
    • 嗯,看了上面的答案,我认为问题出在any_instance(我根本不使用any_instance,它甚至在RSpec中都不存在)
    • 是的,存根查找是有道理的。就模拟/存根而言,这是一个级别,这不是一件好事,但它有效并且比我的解决方法更好。谢谢。
    【解决方案3】:

    好的,现在我明白了。你想存根对外部服务的调用。有趣的是 mocha 不能以这种方式扩展。除了上面提到的之外,似乎是因为存根方法是在单例类上定义的,而不是在模块上,所以不要混进去。

    为什么不这样呢?

    test "bar" do
      Decorator = Module.new{ def foo; 'foo'; end }
      get :bar
    end
    

    如果您不想收到关于 Decorator 已经定义的警告——这暗示着无论如何都会发生一些耦合——你可以注入它:

    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
    

    当然,如果你有更复杂的情况,有多个装饰器,或者装饰器定义了多个方法,这可能不适合你。

    但我认为这比存根查找要好。您通常不希望在集成测试中存根模型。

    【讨论】:

    • 感谢 Eric 的回复。这实际上是我已经在做的(几乎)。基本上这个和bandito的回复是实际工作的两个选项。
    【解决方案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 ...
    

    根据我的经验,集成测试中的模拟通常是一种代码异味。

    【讨论】:

    • 不,我不想测试控制器操作(以及方法)的返回值。我只想模拟扩展方法,从而“剪切”测试对象的实际操作(这是一个外部调用)。 Mocha 无法记录与此对象的交互。
    猜你喜欢
    • 2011-09-25
    • 2012-10-24
    • 1970-01-01
    • 2013-02-24
    • 2014-08-16
    • 2016-01-21
    • 2013-08-25
    • 1970-01-01
    • 2021-02-09
    相关资源
    最近更新 更多