3

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

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 

这是否有可能,或者您是否有其他方式来获得此功能???

4

4 回答 4

2

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

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

我得出了这个结论,试图在expects中遵循调用方法的堆栈。期望Mocha::Central中调用存根,在 Mocha::ClassMethod 中调用存根,在Mocha :: AnyInstanceMethod中调用 *hide_original_method* 。在那里,*hide_original_method* 没有找到任何隐藏的方法并且什么也不做。然后Model.foo方法没有别名为 stubbed mocha 方法,应该调用它来实现您的 mocha 期望,而是调用实际的Model.foo方法,您动态附加到控制器内的 Model 实例的方法。

我的回答是不可能做到这一点。

于 2011-10-09T16:50:46.100 回答
1

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

我通常包括装饰器(而不是在运行时扩展它们)并且我避免使用 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
于 2011-10-09T15:21:27.363 回答
1

好的,现在我明白了。您想要停止对外部服务的调用。有趣的是 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

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

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

于 2011-10-12T02:23:40.117 回答
0

如果您想测试 :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 ...

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

于 2011-10-09T15:28:32.973 回答