0

我有一个像这样的模块,我正在尝试为其编写单元测试

module MyThing
  module Helpers
    def self.generate_archive
      # ...
      ::Configuration.export(arg)
    rescue ::Configuration::Error => error
      raise error
    end
  end
end

由于我无法控制的原因,该::Configuration模块不能存在于我的单元测试环境中,因此我需要将其排除。到目前为止,这是我想出的。

RSpec.describe 'MyThing' do
  it 'generates an archive' do
    configuration_stub = stub_const("::Configuration", Module.new)
    configuration_error_stub = stub_const("::Configuration::Error", Class.new)

    expect_any_instance_of(configuration_stub).to receive(:export).with("arg")
    MyThing::Helpers.generate_archive
  end
end

这给我一个错误。

NoMethodError:
  Undefined method `export' for Configuration:Module

如果我将定义与这样的configuration_stub内联expect_any_instance_of

RSpec.describe 'MyThing' do
  it 'generates an archive' do
    configuration_error_stub = stub_const("::Configuration::Error", Class.new)

    expect_any_instance_of(stub_const("::Configuration", Module.new)).to receive(:export).with("arg")
    MyThing::Helpers.generate_archive
  end
end

我也得到一个错误。

NameError:
  Uninitialized constant Configuration::Error
...
# --- Caused by: ---
# NoMethodError:
#   Undefined method `export' for Configuration:Module
4

1 回答 1

1

expect_any_instance_of适用于实例方法。export被称为类方法。

相反,在类上使用正常的期望。

expect(Configuration).to receive(:export).with("arg")

注意:没有必要写::Configuration在测试中。是::为了澄清MyThing::Helpers::Configuration和之间Configuration

注意:如果您直接在 Configuration 上调用方法,它可能应该是一个类而不是一个模块。

注意:不要在类上调用方法,而是考虑使用配置对象。App.config.export其中App.config返回默认配置对象。这更灵活。

问题是 RSpec 将验证Configuration.export存在。它没有。你可以关闭验证,或者你可以创建一个真实的类来测试。

  before {
    stub_const(
      "Configuration",
      Class.new do
        def self.export(*args)
        end
      end
    )
  }

  it 'exports' do
    expect(Configuration).to receive(:export).with("arg")
    Configuration.export("arg")
  end

您可以编写一个仅用于测试的存根配置模块并将其放入规范/支持中,但是您的测试越来越脱离现实。

真正的问题是你的项目应该有一个真正的配置类!

我猜测真正的配置包含无法签入存储库的生产信息。这是一种常见的反模式。配置模块应隐藏配置值来自何处的详细信息。应该有独立的开发、测试和生产配置。有很多方法可以做到这一点,最常见的是使用环境特定的配置文件,或者将环境特定的配置值存储在环境变量中。


虽然您可以模拟Configuration::Error异常,但您的测试环境中应该没有理由Configuration::Error不存在。添加它并模拟如下错误:

  context 'when Configuration.export raises an error'
    before {
      allow(Configuration).to receive(:export).and_raise(Configuration::Error)
    }

    it 'does whatever its supposed to do' do
    end
  end
于 2022-02-09T19:43:41.090 回答