定义自定义匹配器
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。
错误处理
确保您的匹配器返回 true
或 false
。注意在您的匹配器中适当地处理异常,例如,在大多数情况下,您可能希望您的匹配器在发生异常(例如 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 |