rspec-matchers
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.
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.
| name | RSpec Matchers |
|---|---|
| description | 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. |
| version | 1.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 guidereferences/matcher-aliases.md- Full list of matcher aliases
Example Files
examples/custom_matchers.rb- Working custom matcher examples