RSpec Expectations: You’re Probably Using the Wrong Matcher

RSpec Expectations: You’re Probably Using the Wrong Matcher

August 3, 2026

If you’ve been writing Ruby tests for a while, chances are you’ve reached for eq more times than you can count. While it works for many scenarios, RSpec Expectations offers a much richer vocabulary for expressing intent.

Choosing the right matcher isn’t just about making a test pass—it’s about communicating exactly what behavior your code should exhibit.

Let’s explore some of the most useful matcher categories and when each one should be used.


Tokyo Topographic Map
Built for Ruby on Rails

Build Maps Without
Google APIs

Generate beautiful production-ready maps directly from your Rails backend. Fast rendering, zero external dependencies, full control.

✓ No API fees ✓ Self-hosted ✓ Rails Native ✓ Fast Rendering
Why developers switch
Replace expensive map stacks.

Stop relying on third-party map billing and bloated JS libraries. Render static or dynamic maps directly in Ruby.

Try It Now
Tokyo MapView Demo

Equality Isn’t Just Equality

One of the first surprises for many developers is that RSpec provides multiple ways to compare objects.

expect(actual).to eq(expected)
expect(actual).to eql(expected)
expect(actual).to be(expected)
expect(actual).to equal(expected)

Although these look similar, they’re designed for different purposes.

  • eq succeeds when actual == expected.
  • eql relies on Ruby’s eql? method.
  • be and equal verify object identity using equal?.

The distinction becomes important when comparing value objects versus checking that two variables reference the exact same object.

Selecting the appropriate matcher makes your expectations more explicit and easier to understand.


Truthiness vs. Boolean Values

Ruby treats every object except nil and false as truthy.

RSpec reflects this distinction with dedicated matchers.

expect(user).to be_truthy
expect(result).to be true
expect(error).to be_falsy
expect(flag).to be false
expect(record).to be_nil

These matchers let you communicate whether you’re expecting a strict Boolean value or simply a truthy or falsy result.


Working with Collections

RSpec includes specialized matchers for collections, allowing tests to describe exactly what matters.

Exact Order

expect(numbers).to eq([1, 2, 3])

Partial Matches

expect(numbers).to start_with(1)
expect(numbers).to end_with(3)
expect(numbers).to include(2)

Ignore Ordering

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

Instead of manually sorting arrays or writing verbose assertions, these matchers describe your intent directly.


Expectations on Blocks

Some behavior isn’t about return values.

Sometimes what matters is what happens while executing a block.

RSpec provides dedicated matchers for these situations.

Errors

expect { service.call }.to raise_error
expect { service.call }.to raise_error(MyError)
expect { service.call }.to raise_error("Something went wrong")

Yielding

For methods that accept blocks, Expectations includes several yield matchers.

expect { |b| 5.tap(&b) }.to yield_control
expect { |b| 5.tap(&b) }.to yield_with_args(5)
expect { |b| [1,2,3].each(&b) }
.to yield_successive_args(1,2,3)

These allow tests to verify block behavior without relying on implementation details.


Composing Expectations

One of the more expressive capabilities in RSpec Expectations is combining matchers.

expect(alphabet)
.to start_with("a")
.and end_with("z")

You can also compose matchers inside other matchers.

expect(hash).to match(
a: {
b: a_collection_containing_exactly(
a_string_starting_with("f"),
an_instance_of(Integer)
)
}
)

Rather than comparing entire complex objects, you can specify only the characteristics that matter.

This results in tests that are both more flexible and easier to read.


Choosing the Right Matcher

A good expectation answers a very specific question.

  • Are you comparing values?
  • Are you checking object identity?
  • Does order matter?
  • Are you validating part of a collection?
  • Should an exception be raised?
  • Does a method yield to a block?

RSpec includes dedicated matchers for each of these scenarios. Choosing the one that best matches your intent makes your tests more expressive and easier to maintain.


Final Thoughts

RSpec Expectations goes far beyond eq. It provides a comprehensive set of matchers for values, identity, collections, exceptions, yielding behavior, and composable assertions.

The next time you write an expectation, take a moment to ask whether eq is really expressing what you mean. More often than not, there’s a matcher that communicates your intent more precisely—and that’s exactly what good tests should do.

Leave a comment