谓词匹配器

Ruby 对象通常提供谓词方法

  7.zero?                  # => false
  0.zero?                  # => true
  [1].empty?               # => false
  [].empty?                # => true
  { :a => 5 }.has_key?(:b) # => false
  { :b => 5 }.has_key?(:b) # => true

您可以使用基本相等匹配器对这些方法设置期望

  expect(7.zero?).to eq true # fails with "expected true, got false (using ==)"

…但 RSpec 提供了更具可读性且提供更好失败输出的动态谓词匹配器。

对于任何谓词方法,RSpec 都提供了相应的匹配器。只需在方法名前缀添加 be_ 并删除问号。示例

  expect(7).not_to be_zero       # calls 7.zero?
  expect([]).to be_empty         # calls [].empty?
  expect(x).to be_multiple_of(3) # calls x.multiple_of?(3)

或者,对于以 has_ 开头的谓词方法,例如 Hash#has_key?,RSpec 允许您使用另一种形式,因为 be_has_key 毫无意义。

  expect(hash).to have_key(:foo)       # calls hash.has_key?(:foo)
  expect(array).not_to have_odd_values # calls array.has_odd_values?

在这两种情况下,RSpec 都提供了清晰易懂的错误消息,例如

预期 zero? 为真,得到 false

调用私有方法也会失败

预期 private_method? 返回 true,但它是一个私有方法

传递给匹配器的任何参数都将传递给谓词方法。

预期 subjectbe_zero(基于 Integer#zero?)

给定一个名为 “shouldbezero_spec.rb” 的文件,其中包含

RSpec.describe 0 do
  it { is_expected.to be_zero }
end

RSpec.describe 7 do
  it { is_expected.to be_zero } # deliberate failure
end

我运行 rspec should_be_zero_spec.rb

那么输出应该包含 “2 个示例,1 个失败”

并且输出应该包含 “预期 7.zero? 为真,得到 false”。

预期 subject 不为 be_empty(基于 Array#empty?)

给定一个名为 “shouldnotbeemptyspec.rb” 的文件,其中包含

RSpec.describe [1, 2, 3] do
  it { is_expected.not_to be_empty }
end

RSpec.describe [] do
  it { is_expected.not_to be_empty } # deliberate failure
end

我运行 rspec should_not_be_empty_spec.rb

那么输出应该包含 “2 个示例,1 个失败”

并且输出应该包含 “预期 [].empty? 为假,得到 true”。

预期 subject have_key(基于 Hash#has_key?)

给定一个名为 “shouldhavekey_spec.rb” 的文件,其中包含

RSpec.describe Hash do
  subject { { :foo => 7 } }
  it { is_expected.to have_key(:foo) }
  it { is_expected.to have_key(:bar) } # deliberate failure
end

我运行 rspec should_have_key_spec.rb

那么输出应该包含 “2 个示例,1 个失败”

并且输出应该包含 “预期 {:foo=>7}.has_key?(:bar) 为真,得到 false”。

预期 subject 具有所有小数(基于自定义 has_decimals? 方法)

给定一个名为 “shouldnothaveallstringkeysspec.rb” 的文件,其中包含

class Float
  def has_decimals?
    round != self
  end
end

RSpec.describe Float do
  context 'with decimals' do
    subject { 4.2 }

    it { is_expected.to have_decimals }
  end

  context 'with no decimals' do
    subject { 42.0 }
    it { is_expected.to have_decimals } # deliberate failure
  end
end

我运行 rspec should_not_have_all_string_keys_spec.rb

那么输出应该包含 “2 个示例,1 个失败”

并且输出应该包含 “预期 42.0.has_decimals? 为真,得到 false”。

匹配器参数将传递给谓词方法

给定一个名为 “predicatematcherargument_spec.rb” 的文件,其中包含

class Integer
  def multiple_of?(x)
    (self % x).zero?
  end
end

RSpec.describe 12 do
  it { is_expected.to be_multiple_of(3) }
  it { is_expected.not_to be_multiple_of(7) }

  # deliberate failures
  it { is_expected.not_to be_multiple_of(4) }
  it { is_expected.to be_multiple_of(5) }
end

我运行 rspec predicate_matcher_argument_spec.rb

那么输出应该包含 “4 个示例,2 个失败”

并且输出应该包含 “预期 12.multiple_of?(4) 为假,得到 true”

并且输出应该包含 “预期 12.multiple_of?(5) 为真,得到 false”。

配置 strict_predicate_matchers 会影响对 truefalse 之外结果的匹配

给定一个名为 “strictornot.rb” 的文件,其中包含

class StrangeResult
  def has_strange_result?
    42
  end
end

RSpec.describe StrangeResult do
  subject { StrangeResult.new }

  before do
    RSpec.configure do |config|
      config.expect_with :rspec do |expectations|
        expectations.strict_predicate_matchers = strict
      end
    end
  end

  context 'with non-strict matchers (default)' do
    let(:strict) { false }
    it { is_expected.to have_strange_result }
  end

  context 'with strict matchers' do
    let(:strict) { true }
    # deliberate failure
    it { is_expected.to have_strange_result }
  end
end

我运行 rspec strict_or_not.rb

那么输出应该包含 “2 个示例,1 个失败”

并且输出应该包含 “hasstrangeresult?` 返回 true,得到 42”。

使用 be_predicate 调用私有方法会导致错误

给定一个名为 “attemptingtomatchprivatemethod_spec.rb” 的文件,其中包含

class WithPrivateMethods
  def secret?
    true
  end
  private :secret?
end

RSpec.describe 'private methods' do
  subject { WithPrivateMethods.new }

  # deliberate failure
  it { is_expected.to be_secret }
end

我运行 rspec attempting_to_match_private_method_spec.rb

那么输出应该包含 “1 个示例,1 个失败”

并且输出应该包含 “secret? 是一个私有方法”。

使用 have_predicate 调用私有方法会导致错误

给定一个名为 “attemptingtomatchprivatemethod_spec.rb” 的文件,其中包含

class WithPrivateMethods
  def has_secret?
    true
  end
  private :has_secret?
end

RSpec.describe 'private methods' do
  subject { WithPrivateMethods.new }

  # deliberate failure
  it { is_expected.to have_secret }
end

我运行 rspec attempting_to_match_private_method_spec.rb

那么输出应该包含 “1 个示例,1 个失败”

并且输出应该包含 “has_secret? 是一个私有方法”。