定义自定义匹配器

rspec-expectations 提供了一个 DSL 用于定义自定义匹配器。这些通常用于在应用程序域中表达期望。

在幕后,RSpec::Matchers.define 在单例类的上下文中评估 define 块。如果您需要编写更复杂的匹配器并希望自己使用 Class 方法,请访问我们的 API 文档并阅读有关 MatcherProtocol文档

使用默认消息定义匹配器

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

require 'rspec/expectations'

RSpec::Matchers.define :be_a_multiple_of do |expected|
  match do |actual|
    actual % expected == 0
  end
end

RSpec.describe 9 do
  it { is_expected.to be_a_multiple_of(3) }
end

RSpec.describe 9 do
  it { is_expected.not_to be_a_multiple_of(4) }
end

# fail intentionally to generate expected output
RSpec.describe 9 do
  it { is_expected.to be_a_multiple_of(4) }
end

# fail intentionally to generate expected output
RSpec.describe 9 do
  it { is_expected.not_to be_a_multiple_of(3) }
end

我运行 rspec ./matcher_with_default_message_spec.rb --format documentation

那么退出状态不应该为 0

并且输出应该包含“预期为 3 的倍数”

并且输出应该包含“预期不为 4 的倍数”

并且输出应该包含“失败/错误: it { isexpected.to beamultipleof(4) }”

并且输出应该包含“失败/错误: it { isexpected.notto beamultiple_of(3) }”

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

并且输出应该包含“预期 9 为 4 的倍数”

并且输出应该包含“预期 9 不为 3 的倍数”。

覆盖 failure_message

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

require 'rspec/expectations'

RSpec::Matchers.define :be_a_multiple_of do |expected|
  match do |actual|
    actual % expected == 0
  end
  failure_message do |actual|
    "expected that #{actual} would be a multiple of #{expected}"
  end
end

# fail intentionally to generate expected output
RSpec.describe 9 do
  it { is_expected.to be_a_multiple_of(4) }
end

我运行 rspec ./matcher_with_failure_message_spec.rb

那么退出状态不应该为 0

并且stdout 应该包含“1 个示例,1 个失败”

并且stdout 应该包含“预期 9 为 4 的倍数”。

覆盖 failuremessagewhen_negated

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

require 'rspec/expectations'

RSpec::Matchers.define :be_a_multiple_of do |expected|
  match do |actual|
    actual % expected == 0
  end
  failure_message_when_negated do |actual|
    "expected that #{actual} would not be a multiple of #{expected}"
  end
end

# fail intentionally to generate expected output
RSpec.describe 9 do
  it { is_expected.not_to be_a_multiple_of(3) }
end

我运行 rspec ./matcher_with_failure_for_message_spec.rb

那么退出状态不应该为 0

并且stdout 应该包含“1 个示例,1 个失败”

并且stdout 应该包含“预期 9 不为 3 的倍数”。

覆盖 description

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

require 'rspec/expectations'

RSpec::Matchers.define :be_a_multiple_of do |expected|
  match do |actual|
    actual % expected == 0
  end
  description do
    "be multiple of #{expected}"
  end
end

RSpec.describe 9 do
  it { is_expected.to be_a_multiple_of(3) }
end

RSpec.describe 9 do
  it { is_expected.not_to be_a_multiple_of(4) }
end

我运行 rspec ./matcher_overriding_description_spec.rb --format documentation

那么退出状态应该为 0

并且stdout 应该包含“2 个示例,0 个失败”

并且stdout 应该包含“预期为 3 的倍数”

并且stdout 应该包含“预期不为 4 的倍数”。

没有参数

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

require 'rspec/expectations'

RSpec::Matchers.define :have_7_fingers do
  match do |thing|
    thing.fingers.length == 7
  end
end

class Thing
  def fingers; (1..7).collect {"finger"}; end
end

RSpec.describe Thing do
  it { is_expected.to have_7_fingers }
end

我运行 rspec ./matcher_with_no_args_spec.rb --format documentation

那么退出状态应该为 0

并且stdout 应该包含“1 个示例,0 个失败”

并且stdout 应该包含“预期有 7 根手指”。

具有多个参数

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

require 'rspec/expectations'

RSpec::Matchers.define :be_the_sum_of do |a,b,c,d|
  match do |sum|
    a + b + c + d == sum
  end
end

RSpec.describe 10 do
  it { is_expected.to be_the_sum_of(1,2,3,4) }
end

我运行 rspec ./matcher_with_multiple_args_spec.rb --format documentation

那么退出状态应该为 0

并且stdout 应该包含“1 个示例,0 个失败”

并且stdout 应该包含“预期为 1、2、3 和 4 的总和”。

具有块参数

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

require 'rspec/expectations'

RSpec::Matchers.define :be_lazily_equal_to do
  match do |obj|
    obj == block_arg.call
  end

  description { "be lazily equal to #{block_arg.call}" }
end

RSpec.describe 10 do
  it { is_expected.to be_lazily_equal_to { 10 } }
end

我运行 rspec ./matcher_with_block_arg_spec.rb --format documentation

那么退出状态应该为 0

并且stdout 应该包含“1 个示例,0 个失败”

并且stdout 应该包含“预期懒惰地等于 10”。

使用辅助方法

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

require 'rspec/expectations'

RSpec::Matchers.define :have_same_elements_as do |sample|
  match do |actual|
    similar?(sample, actual)
  end

  def similar?(a, b)
    a.sort == b.sort
  end
end

RSpec.describe "these two arrays" do
  specify "should be similar" do
    expect([1,2,3]).to have_same_elements_as([2,3,1])
  end
end

我运行 rspec ./matcher_with_internal_helper_spec.rb

那么退出状态应该为 0

并且stdout 应该包含“1 个示例,0 个失败”。

在模块中作用域

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

require 'rspec/expectations'

module MyHelpers
  extend RSpec::Matchers::DSL

  matcher :be_just_like do |expected|
    match {|actual| actual == expected}
  end
end

RSpec.describe "group with MyHelpers" do
  include MyHelpers
  it "has access to the defined matcher" do
    expect(5).to be_just_like(5)
  end
end

RSpec.describe "group without MyHelpers" do
  it "does not have access to the defined matcher" do
    expect do
      expect(5).to be_just_like(5)
    end.to raise_exception
  end
end

我运行 rspec ./scoped_matcher_spec.rb

那么stdout 应该包含“2 个示例,0 个失败”。

在示例组中作用域

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

require 'rspec/expectations'

RSpec.describe "group with matcher" do
  matcher :be_just_like do |expected|
    match {|actual| actual == expected}
  end

  it "has access to the defined matcher" do
    expect(5).to be_just_like(5)
  end

  describe "nested group" do
    it "has access to the defined matcher" do
      expect(5).to be_just_like(5)
    end
  end
end

RSpec.describe "group without matcher" do
  it "does not have access to the defined matcher" do
    expect do
      expect(5).to be_just_like(5)
    end.to raise_exception
  end
end

我运行 rspec scoped_matcher_spec.rb

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

具有针对 expect().to 和 expect().not_to 的独立逻辑的匹配器

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

RSpec::Matchers.define :contain do |*expected|
  match do |actual|
    expected.all? { |e| actual.include?(e) }
  end

  match_when_negated do |actual|
    expected.none? { |e| actual.include?(e) }
  end
end

RSpec.describe [1, 2, 3] do
  it { is_expected.to contain(1, 2) }
  it { is_expected.not_to contain(4, 5, 6) }

  # deliberate failures
  it { is_expected.to contain(1, 4) }
  it { is_expected.not_to contain(1, 4) }
end

我运行 rspec matcher_with_separate_should_not_logic_spec.rb

那么输出应该包含以下所有内容

4 个示例,2 个失败
预期 [1, 2, 3] 包含 1 和 4
预期 [1, 2, 3] 不包含 1 和 4

使用 define_method 创建一个具有访问匹配器参数的辅助方法

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

RSpec::Matchers.define :be_a_multiple_of do |expected|
  define_method :is_multiple? do |actual|
    actual % expected == 0
  end
  match { |actual| is_multiple?(actual) }
end

RSpec.describe 9 do
  it { is_expected.to be_a_multiple_of(3) }
  it { is_expected.not_to be_a_multiple_of(4) }

  # deliberate failures
  it { is_expected.to be_a_multiple_of(2) }
  it { is_expected.not_to be_a_multiple_of(3) }
end

我运行 rspec define_method_spec.rb

那么输出应该包含以下所有内容

4 个示例,2 个失败
预期 9 为 2 的倍数
预期 9 不为 3 的倍数

在匹配器中包含一个带有辅助方法的模块

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

module MatcherHelpers
  def is_multiple?(actual, expected)
    actual % expected == 0
  end
end

RSpec::Matchers.define :be_a_multiple_of do |expected|
  include MatcherHelpers
  match { |actual| is_multiple?(actual, expected) }
end

RSpec.describe 9 do
  it { is_expected.to be_a_multiple_of(3) }
  it { is_expected.not_to be_a_multiple_of(4) }

  # deliberate failures
  it { is_expected.to be_a_multiple_of(2) }
  it { is_expected.not_to be_a_multiple_of(3) }
end

我运行 rspec include_module_spec.rb

那么输出应该包含以下所有内容

4 个示例,2 个失败
预期 9 为 2 的倍数
预期 9 不为 3 的倍数

使用 values_match? 比较值和/或复合匹配器。

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

RSpec::Matchers.define :have_content do |expected|
  match do |actual|
    # The order of arguments is important for `values_match?`, e.g.
    # especially if your matcher should handle `Regexp`-objects
    # (`/regex/`): First comes the `expected` value, second the `actual`
    # one.
    values_match? expected, actual
  end
end

RSpec.describe 'a' do
  it { is_expected.to have_content 'a' }
end

RSpec.describe 'a' do
  it { is_expected.to have_content /a/ }
end

RSpec.describe 'a' do
  it { is_expected.to have_content a_string_starting_with('a') }
end

我运行 rspec ./compare_values_spec.rb --format documentation

那么退出状态应该为 0。

错误处理

确保您的匹配器返回 truefalse。注意在您的匹配器中适当地处理异常,例如,在大多数情况下,您可能希望您的匹配器在发生异常(例如 ArgumentError)时返回 false,但在某些情况下,您可能希望将异常传递给用户。

You should handle each `StandardError` with care! Do not handle them all in one.
    match do |actual|
      begin
        '[...] Some code'
      rescue ArgumentError
        false
      end
    end

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

class CustomClass; end

RSpec::Matchers.define :is_lower_than do |expected|
  match do |actual|
    begin
      actual < expected
    rescue ArgumentError
      false
    end
  end
end

RSpec.describe 1 do
  it { is_expected.to is_lower_than 2 }
end

RSpec.describe 1 do
  it { is_expected.not_to is_lower_than 'a' }
end

RSpec.describe CustomClass do
  it { expect { is_expected.not_to is_lower_than 2 }.to raise_error NoMethodError }
end

我运行 rspec ./error_handling_spec.rb --format documentation

那么退出状态应该为 0。

为您的匹配器定义别名

如果您希望您的匹配器在不同的上下文中可读,您可以使用 .alias_matcher 方法为您的匹配器提供别名。

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

RSpec::Matchers.define :be_a_multiple_of do |expected|
  match do |actual|
    actual % expected == 0
  end
end

RSpec::Matchers.alias_matcher :be_n_of , :be_a_multiple_of

RSpec.describe 9 do
  it { is_expected.to be_n_of(3) }
end

我运行 rspec ./alias_spec.rb --format documentation

那么退出状态应该为 0。

具有冒泡的期望错误

默认情况下,匹配块将吞下期望错误(例如,由使用诸如 expect(1).to eq 2 这样的期望引起的),如果您希望允许这些错误冒泡,请传递选项 :notify_expectation_failures => true

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

RSpec::Matchers.define :be_a_palindrome do
  match(:notify_expectation_failures => true) do |actual|
    expect(actual).to be_a(String)
    expect(actual.reverse).to eq(actual)
  end
end

RSpec.describe "a custom matcher that bubbles up expectation errors" do
  it "bubbles expectation errors" do
    expect("carriage").to be_a_palindrome
  end
end

我运行 rspec bubbling_expectation_errors_spec.rb

那么输出应该包含以下所有内容

失败
1) 冒泡期望错误的自定义匹配器冒泡期望错误
失败/错误: expect(actual.reverse).to eq(actual)
预期: “carriage”
得到: “egairrac”
(使用 == 比较)
# ./bubblingexpectationerrors_spec.rb:4
# ./bubblingexpectationerrors_spec.rb:10