【问题标题】:How to avoid using allow_any_instance_of?如何避免使用allow_any_instance_of?
【发布时间】:2016-04-15 08:26:38
【问题描述】:

假设我们有以下代码:

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

但文档建议我们不要使用它because of several reasons。那么如何避免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

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

【问题讨论】:

    标签: ruby rspec rspec-mocks


    【解决方案1】:

    在没有根据Uzbekjon's answer(我同意)进行依赖注入的情况下,您还可以考虑取消对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
    

    【讨论】:

    • 是的,这就是我最终得出的结论。当在一个循环中创建多个对象时,它特别方便。
    【解决方案2】:

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

    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
    

    【讨论】:

    • 然后我们应该使用 double 来伪造 builder 对象,对吧?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-17
    • 2011-11-02
    • 2017-07-09
    • 2015-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多