是否有将隐式主题传递给 rspec 匹配器的概念?

Is there a concept of an implicit subject to be passed to an rspec matcher?

假设我有一个自定义匹配器,用于检查对象是否是有效的 RGB 元组——我们称它为 valid_rgb_tuple。匹配器将对象作为参数并对其进行验证。我希望能够在 oneliner 中使用它,其中 subject 应该是一个元组。理想情况下 look/read 是这样的:

subject { some_method_that_should_return_a_tuple }
it { should be_a_valid_rgb_tuple }

我不确定如何进行这项工作,或者是否有可能接近。我想我需要一种方法来强制主题自动传递给验证器。有没有这样的结构?

更新:

事实证明这只是我定义匹配器的方式有问题。我做了这样的事情:

RSpec::Matchers.define :be_a_valid_rgb_tuple do |color|
  match do
    begin
      # check the color value
    end
  end
end

由于 color 的传递方式,这是不正确的。需要像这样重做:

RSpec::Matchers.define :be_a_valid_rgb_tuple do
  match do |color|
    begin
      # check the color value
    end
  end
end

请注意如何将值传递给提供给匹配方法的块。在第一种情况下,颜色参数是调用 be_a_valid_rgb_tuple 的参数, 创建 匹配器,而在第二种情况下,它是隐式主题本身。

should be_a_valid_rgb_tuple 就是 expect(subject).to be_a_valid_rgb_tuple。加上括号似乎没那么神奇了。

expect(subject()).to(be_a_valid_rgb_tuple())

expect(subject()) 只是将 subject 的 return 值保存在一个对象中并 return 保存它。 tobe_a_valid_rgb_tuple 使用匹配器对象 return 调用,并将存储的 return 值传递给匹配器。

这是 rspec 工作原理的粗略草图。

class Expectation
  def initialize(value)
    @value = value
  end

  def to(matcher)
    raise unless matcher.matches?(@value)
  end
end

class ValidRgbTupleMatcher
  def matches?(value)
    return false unless value.is_a?(RgbTuple)
    return false unless value.valid?
    return true
  end
end

class RgbTuple
  def valid?
    true
  end
end

def expect(value)
  Expectation.new(value)
end

def subject
  RgbTuple.new
end

def be_a_valid_rgb_tuple
  ValidRgbTupleMatcher.new
end

expect(subject).to be_a_valid_rgb_tuple

你会把 be_a_valid_rgb_tuple 写成 custom matcher and use the normal one liner syntax。尝试使用 rspec 文档编写匹配器,如果遇到问题,请添加具体问题。


尽管您可能不需要自定义匹配器。相反,使用 be_a to check the class of the return value, and be_valid to check if its valid. Combine them with and.

it { should be_a(RgbTuple).and be_valid }

相当于...

it {
  tuple = subject
  expect(tuple.is_a?(RgbTuple)).to be true
  expect(tuple.valid?).to be true
}

匹配器实际上并不关心隐式主语。匹配器只取一个实际值和一个可选的期望值。

require 'rspec/expectations'
RSpec::Matchers.define :be_a_multiple_of do |expected|
  match do |actual|
    actual % expected == 0
  end
end

隐式主题实际上只是匹配器如何调用的问题。如果你这样做:

subject { 16 }
it { should be_a_multiple_of 4 }

它只是 expect(subject).to(be_a_multiple_of(4)) 的 shorthand。

“RGB 元组”匹配器的最小实现是:

require 'rspec/expectations'

RSpec::Matchers.define :be_a_valid_rgb_tuple do
  match do |actual|
    # @todo the boring work of handling bad input such as nil
    # and providing better feedback
    actual.select { |n| n.is_a?(Integer) && (0..255).cover?(n) }.length == 3
  end
end

这假定有效的“RGB 元组”是指一个包含 0 到 255 之间的三个整数的数组。