是否可以使用MiniTest测试类方法(例如Foo.bar)?

时间:2012-12-28 05:02:38

标签: ruby minitest

如何在不测试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的响应值,因为那可能随意改变。

2 个答案:

答案 0 :(得分:6)

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

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

  def process
    @foo_class.bar
  end
end

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

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

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

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

答案 1 :(得分:2)

我知道这是一个老问题,但我一直在寻找一个解决方案,不需要修改业务代码来简化测试,我想我想出了一个解决方案。

即使您使用的是Rails,也需要将gem "minitest"添加到Gemfile,然后将require "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的内容时,它会在其上调用.callDocs for the stub method)。在这个例子中。调用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