如何使用 RSpec 通过 Ruby 安全导航测试代码 &.算子?



我想为这种形式的代码编写一个单元测试:

class Foo
  def bar(obj)
    return nil unless obj&.foo
    obj.bar
  end
end

下面是示例测试:

RSpec.describe Foo do
  subject { Foo.new.bar(obj) }
  context 'when obj is nil' do
    let(:obj) { nil }
    it 'does not call :foo' do
      expect(obj).to_not receive(:foo)
      subject
    end
  end
end

此代码返回:

Failures:
  1) Foo when obj is nil does not call :foo
     Failure/Error: expect(obj).to_not receive(:foo)
       An expectation of `:foo` was set on `nil`. To allow expectations on `nil` and suppress this message, set `RSpec::Mocks.configuration.allow_message_expectations_on_nil` to `true`. To disallow expectations on `nil`, set `RSpec::Mocks.configuration.allow_message_expectations_on_nil` to `false`

默认情况下,RSpec 3.x 在对 nil 设置期望时会抱怨。我有一个很大的代码库,所以我无法修改此全局设置以使测试此类代码更简单。全局更改可能会给其他开发人员带来问题。

我知道我可以将代码修改为类似return nil unless obj.present? && obj.foo但我特别好奇如何处理安全导航运算符。很多谷歌搜索已经出现了关于这个话题的zippo。

有什么想法吗?

不是最干净的方法,但在测试前启用对 nil 的期望并在测试后再次禁用它至少会起作用。所以将规格更改为

RSpec.describe Foo do
  subject { Foo.new.bar(obj) }
  context 'when obj is nil' do
    let(:obj) { nil }
    before do 
      RSpec::Mocks.configuration.allow_message_expectations_on_nil = true
    end
    after do 
      RSpec::Mocks.configuration.allow_message_expectations_on_nil = false
    end
    it 'does not call :foo' do
      expect(obj).to_not receive(:foo)
      subject
    end
  end
end

如果它是一个重复的模式,也可以把它放在rspec宏(context 'when obj is nil', nil_mock: true do)的块之前和之后。

但是,我不确定这是否值得,因为弄乱这些全局设置会产生副作用,这可能会导致一些以后难以调试的顺序相关错误。此外,我实际上只会测试foo是否在命令时被调用,这意味着它会改变系统状态并因此具有副作用。如果它只是访问一个变量,或者计算一个值,因此没有副作用,我根本不会费心测试调用,而是专注于测试被测方法的输入和输出。

最新更新