3

如何在不测试方法的行为(已在其他地方测试过)Foo.bar的情况下测试以下示例中调用的方法?bar

# Code
class Alpha
  def process
    Foo.bar
  end
end

以下规范是我到目前为止所拥有的。不幸的是,这种方法会引发“已定义类”警告,因为 Foo 已在我的项目的其他地方定义。

 # Spec
 let(:alpha) { Alpha.new }
 let(:klass) { MiniTest::Mock.new }

 subject { alpha.process }

 it "calls Foo.bar" do
   klass.expect(:bar, '')     # Define method call expectation
   Foo = klass                # Redefine Foo as a mock object
   subject                    # Run method being tested
   klass.verify               # Confirm method was called
 end

我不希望我的测试依赖于Foo类,因为这是一个外部依赖,我不想测试 的响应值Foo.bar,因为它可能会任意改变。

4

2 回答 2

6

为了模拟这样的类,您必须像这样插入一个注入点:

class Alpha
  def initialize(opts = {})
    @foo_class = opts[:foo_class] || Foo
  end

  def process
    @foo_class.bar
  end
end

这是可行的,因为类名只是 Ruby 中的一个常量,它可以像任何其他值一样被赋值。因此,您现在不是硬编码对Foo类的调用,而是在新实例变量指向的任何地方调用该方法。大多数情况下,除非您传递其他内容,否则这仍然是课堂。我通常像在这里一样将这样的模拟参数隐藏为可选参数。我也没有在面向最终用户的文档中包含对它们的引用,因为它们在技术上不是我认为的公共 API 的一部分。Alpha@foo_classFoo

然后在你的测试中,你可以Alpha像这样初始化你的对象:

fooClassMock = MiniTest::Mock.new
fooClassMock.expect(:bar, '')
alpha = Alpha.new(:foo_class => fooClassMock)

你应该得到你正在寻找的结果。

于 2012-12-28T06:03:03.480 回答
3

我知道这是一个老问题,但我一直在寻找一个不需要修改业务代码以使测试更容易的解决方案,我想我想出了一个解决方案。

即使您使用的是 Rails,也需要添加到gem "minitest"您的. Gemfilerequire "minitest/mock"test_helper.rb

it "calls Foo.bar" do
  bar_is_called = false
  bar_lambda = ->{
    bar_is_called = true
  }

  Foo.stub(:bar, bar_lambda) do
    Alpha.new.process
  end
  bar_is_called.must_equal true
end

.stub可以传递一个返回值,或者当传递一个响应的东西时.call,它会调用.call它(存根方法的文档)。在这个例子中。bar_lambda被调用,改变 的值bar_is_called。这验证了Foo.bar被调用的。

它在测试语法中的工作方式也类似:

test 'Foo.bar is called' do
  bar_is_called = false
  bar_lambda = ->{
    bar_is_called = true
  }

  Foo.stub(:bar, bar_lambda) do
    Alpha.new.process
  end
  assert bar_is_called
end
于 2016-09-27T15:09:22.713 回答