Logo: Relish

  1. Sign up
  2. Sign in

Project: RSpec Expectations 3.0

define matcher

rspec-expectations provides a DSL for defining custom matchers.
These are often useful for expressing expectations in the domain
of your application.

Scenarios
define a matcher with default messages
Given
a file named "matcher_with_default_message_spec.rb" with:
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
When
I run rspec ./matcher_with_default_message_spec.rb --format documentation
Then
the exit status should not be 0
And
the output should contain "should be a multiple of 3"
And
the output should contain "should not be a multiple of 4"
And
the output should contain "Failure/Error: it { is_expected.to be_a_multiple_of(4) }"
And
the output should contain "Failure/Error: it { is_expected.not_to be_a_multiple_of(3) }"
And
the output should contain "4 examples, 2 failures"
And
the output should contain "expected 9 to be a multiple of 4"
And
the output should contain "expected 9 not to be a multiple of 3"
overriding the failure_message
Given
a file named "matcher_with_failure_message_spec.rb" with:
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
When
I run rspec ./matcher_with_failure_message_spec.rb
Then
the exit status should not be 0
And
the stdout should contain "1 example, 1 failure"
And
the stdout should contain "expected that 9 would be a multiple of 4"
overriding the failure_message_when_negated
Given
a file named "matcher_with_failure_for_message_spec.rb" with:
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
When
I run rspec ./matcher_with_failure_for_message_spec.rb
Then
the exit status should not be 0
And
the stdout should contain "1 example, 1 failure"
And
the stdout should contain "expected that 9 would not be a multiple of 3"
overriding the description
Given
a file named "matcher_overriding_description_spec.rb" with:
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
When
I run rspec ./matcher_overriding_description_spec.rb --format documentation
Then
the exit status should be 0
And
the stdout should contain "2 examples, 0 failures"
And
the stdout should contain "should be multiple of 3"
And
the stdout should contain "should not be multiple of 4"
with no args
Given
a file named "matcher_with_no_args_spec.rb" with:
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
When
I run rspec ./matcher_with_no_args_spec.rb --format documentation
Then
the exit status should be 0
And
the stdout should contain "1 example, 0 failures"
And
the stdout should contain "should have 7 fingers"
with multiple args
Given
a file named "matcher_with_multiple_args_spec.rb" with:
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
When
I run rspec ./matcher_with_multiple_args_spec.rb --format documentation
Then
the exit status should be 0
And
the stdout should contain "1 example, 0 failures"
And
the stdout should contain "should be the sum of 1, 2, 3, and 4"
with helper methods
Given
a file named "matcher_with_internal_helper_spec.rb" with:
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
When
I run rspec ./matcher_with_internal_helper_spec.rb
Then
the exit status should be 0
And
the stdout should contain "1 example, 0 failures"
scoped in a module
Given
a file named "scoped_matcher_spec.rb" with:
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
When
I run rspec ./scoped_matcher_spec.rb
Then
the stdout should contain "2 examples, 0 failures"
scoped in an example group
Given
a file named "scoped_matcher_spec.rb" with:
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
When
I run rspec scoped_matcher_spec.rb
Then
the output should contain "3 examples, 0 failures"
matcher with separate logic for should and should_not
Given
a file named "matcher_with_separate_should_not_logic_spec.rb" with:
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
When
I run rspec matcher_with_separate_should_not_logic_spec.rb
Then
the output should contain all of these:
4 examples, 2 failures
expected [1, 2, 3] to contain 1 and 4
expected [1, 2, 3] not to contain 1 and 4
use define_method to create a helper method with access to matcher params
Given
a file named "define_method_spec.rb" with:
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
When
I run rspec define_method_spec.rb
Then
the output should contain all of these:
4 examples, 2 failures
expected 9 to be a multiple of 2
expected 9 not to be a multiple of 3
include a module with helper methods in the matcher
Given
a file named "include_module_spec.rb" with:
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
When
I run rspec include_module_spec.rb
Then
the output should contain all of these:
4 examples, 2 failures
expected 9 to be a multiple of 2
expected 9 not to be a multiple of 3

Last published about 1 month ago by myronmarston.