rspec-matchers

bastos's avatarfrom bastos

This skill should be used when the user asks about "RSpec matchers", "expect syntax", "custom matchers", "compound matchers", "should vs expect", mentions "eq", "be", "include", "match", "have_attributes", or needs guidance on RSpec assertions and expectations.

0stars🔀0forks📁View on GitHub🕐Updated Jan 10, 2026

When & Why to Use This Skill

This Claude skill provides a comprehensive reference and expert guidance for RSpec Matchers, the fundamental building blocks of Ruby testing. It enables developers to master the 'expect' syntax, navigate various assertion types—including equality, collections, and truthiness—and implement advanced testing patterns like compound matchers and custom assertions for more expressive and reliable test suites.

Use Cases

  • Scenario 1: Selecting the most appropriate matcher for complex data structures, such as using 'contain_exactly' for order-independent array comparison or 'have_attributes' for object state verification.
  • Scenario 2: Debugging test logic by understanding the nuances between value equality (eq), type-strict equality (eql), and object identity (equal/be).
  • Scenario 3: Verifying side effects and behavioral changes, such as checking if a method call changes a database count or updates a specific object attribute using the 'change' matcher.
  • Scenario 4: Improving test readability and maintainability by designing custom domain-specific matchers with tailored failure messages for unique business logic.
nameRSpec Matchers
descriptionThis skill should be used when the user asks about "RSpec matchers", "expect syntax", "custom matchers", "compound matchers", "should vs expect", mentions "eq", "be", "include", "match", "have_attributes", or needs guidance on RSpec assertions and expectations.
version1.0.0

RSpec Matchers

Matchers are the building blocks of RSpec expectations. They define what you're testing and how values should compare.

Basic Syntax

expect(actual).to matcher(expected)
expect(actual).not_to matcher(expected)   # or to_not

Equality Matchers

eq - Value Equality

Tests value equality using ==:

expect(5).to eq(5)
expect("hello").to eq("hello")
expect([1, 2, 3]).to eq([1, 2, 3])

eql - Value and Type Equality

Tests using eql? (same value and type):

expect(5).to eql(5)
expect(5.0).not_to eql(5)  # Different types

equal / be - Identity

Tests object identity using equal?:

a = "hello"
b = a
expect(a).to equal(b)     # Same object
expect(a).to be(b)        # Alias for equal

c = "hello"
expect(a).not_to equal(c) # Different objects, same value

Comparison Matchers

expect(10).to be > 5
expect(10).to be >= 10
expect(10).to be < 20
expect(10).to be <= 10
expect(10).to be_between(5, 15).inclusive
expect(10).to be_between(5, 15).exclusive
expect(10).to be_within(0.1).of(10.05)

Truthiness Matchers

expect(true).to be true
expect(false).to be false
expect(nil).to be nil
expect(nil).to be_nil

expect(1).to be_truthy      # Truthy (not nil/false)
expect(nil).to be_falsey    # Falsey (nil or false)

Type Matchers

expect(user).to be_a(User)
expect(user).to be_an(Admin)        # Alias
expect(user).to be_an_instance_of(User)
expect(user).to be_a_kind_of(User)  # Includes subclasses

Collection Matchers

include

expect([1, 2, 3]).to include(2)
expect([1, 2, 3]).to include(1, 3)
expect("hello world").to include("world")
expect({ a: 1, b: 2 }).to include(a: 1)
expect({ a: 1, b: 2 }).to include(:a)

contain_exactly

Matches array with exact elements, any order:

expect([1, 2, 3]).to contain_exactly(3, 2, 1)
expect([1, 2, 3]).to match_array([3, 1, 2])  # Alias

start_with / end_with

expect([1, 2, 3]).to start_with(1)
expect([1, 2, 3]).to start_with(1, 2)
expect("hello").to start_with("he")
expect("hello").to end_with("lo")

all

Every element matches:

expect([1, 3, 5]).to all(be_odd)
expect(users).to all(be_valid)
expect(numbers).to all(be > 0)

have_attributes

expect(user).to have_attributes(name: "John", age: 30)
expect(user).to have_attributes(
  name: a_string_starting_with("J"),
  age: a_value > 18
)

Predicate Matchers

Any method ending in ? becomes a matcher with be_:

expect(user).to be_valid       # calls user.valid?
expect(list).to be_empty       # calls list.empty?
expect(user).to be_admin       # calls user.admin?
expect(order).to be_pending    # calls order.pending?

For has_* methods, use have_:

expect(hash).to have_key(:name)    # calls hash.has_key?(:name)
expect(user).to have_permissions   # calls user.has_permissions?

String Matchers

expect("hello world").to match(/world/)
expect("hello world").to match("world")
expect("hello").to start_with("he")
expect("hello").to end_with("lo")
expect("hello").to include("ell")

Change Matcher

Test side effects:

expect { user.save }.to change(User, :count).by(1)
expect { user.save }.to change(User, :count).from(0).to(1)
expect { user.activate! }.to change(user, :active).from(false).to(true)

# Block syntax for complex changes
expect { order.complete! }.to change { order.reload.status }.to("completed")

Raise and Throw Matchers

expect { raise "error" }.to raise_error
expect { raise "error" }.to raise_error("error")
expect { raise ArgumentError }.to raise_error(ArgumentError)
expect { raise ArgumentError, "bad arg" }.to raise_error(ArgumentError, "bad arg")
expect { raise ArgumentError, "bad arg" }.to raise_error(ArgumentError, /bad/)

# Throw
expect { throw :done }.to throw_symbol
expect { throw :done }.to throw_symbol(:done)
expect { throw :done, 42 }.to throw_symbol(:done, 42)

Output Matchers

expect { puts "hello" }.to output("hello\n").to_stdout
expect { warn "error" }.to output(/error/).to_stderr
expect { print "hi" }.to output("hi").to_stdout

Compound Matchers

Combine matchers with and / or:

expect(user.age).to be >= 18 and be < 65
expect(string).to start_with("Hello").and end_with("!")
expect(result).to eq(1).or eq(2)

# Using described_as for better failure messages
expect(value).to(be > 0).and(be < 100).described_as("between 0 and 100")

Composable Matchers

Use matchers as arguments to other matchers:

expect(users).to include(
  a_user_with(name: "John"),
  a_user_with(name: "Jane")
)

expect([1, 2, 3]).to include(a_value > 2)
expect(response).to have_attributes(
  status: a_value_between(200, 299),
  body: a_string_including("success")
)

Built-in Aliases

a_string_starting_with("hello")
a_string_ending_with("world")
a_string_including("test")
a_string_matching(/pattern/)
a_value_between(1, 10)
a_value_within(0.1).of(5.0)
a_hash_including(key: value)
an_instance_of(User)
a_kind_of(User)
an_object_eq_to(expected)

Custom Matchers

Simple Custom Matcher

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

expect(9).to be_a_multiple_of(3)

With Failure Messages

RSpec::Matchers.define :be_valid_email do
  match do |actual|
    actual =~ /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
  end

  failure_message do |actual|
    "expected #{actual.inspect} to be a valid email address"
  end

  failure_message_when_negated do |actual|
    "expected #{actual.inspect} not to be a valid email address"
  end
end

With Chain Methods

RSpec::Matchers.define :have_errors_on do |attribute|
  match do |model|
    model.valid?
    @errors = model.errors[attribute]
    @errors.present? && (@message.nil? || @errors.include?(@message))
  end

  chain :with_message do |message|
    @message = message
  end

  failure_message do |model|
    "expected #{model.class} to have errors on #{attribute}"
  end
end

expect(user).to have_errors_on(:email).with_message("is invalid")

Matcher Cheat Sheet

Matcher Description
eq(x) Value equality (==)
eql(x) Value + type equality
equal(x) / be(x) Object identity
be_truthy Not nil/false
be_falsey Nil or false
be > x Comparison
include(x) Contains element
match(/x/) Regex match
raise_error(E) Raises exception
change { } Side effect
have_attributes(h) Object attributes
all(matcher) All elements match
contain_exactly(...) Exact elements, any order

Additional Resources

Reference Files

For advanced patterns:

  • references/custom-matchers.md - Complete custom matcher guide
  • references/matcher-aliases.md - Full list of matcher aliases

Example Files

  • examples/custom_matchers.rb - Working custom matcher examples