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.
/plugin marketplace add bastos/claude-code-plugin-marketplace/plugin install bastos-rspec-plugins-rspec@bastos/claude-code-plugin-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Matchers are the building blocks of RSpec expectations. They define what you're testing and how values should compare.
expect(actual).to matcher(expected)
expect(actual).not_to matcher(expected) # or to_not
Tests value equality using ==:
expect(5).to eq(5)
expect("hello").to eq("hello")
expect([1, 2, 3]).to eq([1, 2, 3])
Tests using eql? (same value and type):
expect(5).to eql(5)
expect(5.0).not_to eql(5) # Different types
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
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)
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)
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
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)
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
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")
Every element matches:
expect([1, 3, 5]).to all(be_odd)
expect(users).to all(be_valid)
expect(numbers).to all(be > 0)
expect(user).to have_attributes(name: "John", age: 30)
expect(user).to have_attributes(
name: a_string_starting_with("J"),
age: a_value > 18
)
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?
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")
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")
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)
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
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")
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")
)
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)
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)
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
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 | 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 |
For advanced patterns:
references/custom-matchers.md - Complete custom matcher guidereferences/matcher-aliases.md - Full list of matcher aliasesexamples/custom_matchers.rb - Working custom matcher examples