12

假设我们有以下代码:

class A
  def create_server
    options = {
      name: NameBuilder.new.build_name
    }
    do_some_operations(options)
  end
end

为了测试这些方法,我曾经使用过allow_any_instance_of

it 'does operations' do
  allow_any_instance_of(NameBuilder).to receive(:build_name)
  # test body
end

但是由于几个原因,文档建议我们不要使用它。那么如何避免allow_any_instance_of呢?我只找到了一种解决方案:

class A
  def create_server
    options = {
      name: builder.build_name
    }
    do_some_operations
  end

  private

  def builder
    NameBuilder.new
  end
end

但是使用这种方法,代码很快就会充满几乎无用的方法(尤其是当您在所描述的类中积极使用不同对象的组合时)。

4

2 回答 2

10

根据Uzbekjon 的回答(我同意),在没有依赖注入的情况下,您还可以考虑取消调用,NameBuilder.new以便您可以直接控制NameBuilder被测实例:

class NameBuilder
  def build_name
    # builds name...
  end
end

class A
  def create_server
    options = {
      name: NameBuilder.new.build_name
    }
    do_some_operations(options)
  end

  def do_some_operations(options)
    # does some operations
  end
end

RSpec.describe A do
  let(:a) { described_class.new }

  describe '#create_server' do
    let(:name_builder) { instance_double(NameBuilder) }

    before do
      allow(NameBuilder).to receive(:new).and_return(name_builder)
    end

    it 'does operations' do
      # the first expectation isn't really part of what you seem
      # to want to test, but it shows that this way of testing can work
      expect(name_builder).to receive(:build_name)
      expect(a).to receive(:do_some_operations)
      a.create_server
    end
  end
end
于 2016-04-17T10:59:26.923 回答
3

如果很难测试,说明你的类设计有问题。在您的情况下,当您对类中特定类的特定方法调用进行测试时,您正在测试如下:

allow_any_instance_of(NameBuilder).to receive(:build_name)

您的测试确切地知道该方法是如何在内部实现的。您的类应该封装逻辑并将其隐藏。你做的恰恰相反。

应该测试任何内部方法逻辑。只是测试行为。给出输入并测试输出的正确性。

如果您真的想在NameBuilder类上测试该方法调用,请注入该依赖项并使您的类更具可测试性。这也遵循 OOP 原则。

class A
  def create_server(builder)
    do_some_operations(name: builder.build_name)
  end
end
于 2016-04-15T09:49:16.270 回答