Katie demonstrating RSpec matchers in Ruby and Rails during the "This Week I Learned" series to improve testing techniques.

Welcome to TWIL, our week-in-review of micro learnings that effortlessly steamroll the complexities of software development into digestible insights. This week, Katie reveals the power of RSpec: Expect to receive with matchers in the Ruby and Rails ecosystem. Discover how RSpec's matchers refine tests, offering precise control over method calls and argument expectations, while ensuring your tests remain robust and intuitive.

RSpec: Expect to Receive with Matchers

With RSpec, you can stub a method call in your tests to prevent the actual method being called and optionally dictate the method call’s return value:

allow(Thing).to receive(:some_method)
allow(something).to receive(:some_other_method).and_return("whatever")
# Thing.some_method or something.some_other_method will no longer call
# the actual methods; additionally, something.some_other_method will 
# return the specified value ("whatever" in this example).

Stubbing a method like this also allows you to test that the method was called by other code:

# Code that should trigger the method calls, in this case
# Thing.some_method("some", "arguments") and something.some_other_method(*)
expect(Thing).to have_received(:some_method).with("some", "arguments")
expect(something).to have_received(:some_other_method)

This is very handy, but what if you want to ensure that your method is called with, for example, a specific ActiveRecord result?

# You will likely already have the objects in question set up already:
let(:thing_1) { create(:thing) }
let(:thing_2) { create(:thing) }

# Actual argument: #<ActiveRecord::Relation [#<Thing id: 1>, #<Thing id: 2>]>
array_of_things = [thing_1, thing_2]

# Code here that should call Thing.some_method with ActiveRecord relation

expect(Thing).to have_received(:some_method).with(array_of_things)
# ☝️ This will fail, because the spec as written expects the method to be
# called with an array but receives an ActiveRecord relation instead

It can be challenging to set up tests for method calls that expect more complex arguments as above, except… RSpec will accept matchers in place of explicit arguments!

# You will likely already have the objects in question set up already:
let(:thing_1) { create(:thing) }
let(:thing_2) { create(:thing) }

# Actual argument: #<ActiveRecord::Relation [#<Thing id: 1>, #<Thing id: 2>]>
array_of_things = [thing_1, thing_2]

# Code here that should call Thing.some_method with ActiveRecord relation

expect(Thing).to have_received(:some_method).with(
  contain_exactly(thing_1, thing_2),
)
# This amounts to a check for whether whatever Thing.some_method was, in fact,
# called with does contain_exactly(array_of_things)

This should work for any RSpec matcher, and there are even aliases (from RSpec 3) that can be used to improve readability, e.g., a_collection_containing_exactly instead of contain_exactly:

expect(Thing).to have_received(:some_method).with(
  a_collection_containing_exactly(array_of_things),
)

Resources

  • Ruby
  • Rails
  • Tests
Katie Linero's profile picture
Katie Linero

Senior Software Engineer

Related Posts

MCP (Model Context Protocol) logo — a stylized white interlinked letter mark — centered on an abstract background of flowing purple and orange gradient waves, representing AI connectivity and data integration.
November 25, 2025 • Frank Valcarcel

Anthropic’s Model Context Protocol: The Standard for AI Tool Integration

A year after launch, Anthropic’s Model Context Protocol has become the universal standard for connecting AI agents to enterprise tools. Backed by OpenAI, Google, Microsoft, and the Linux Foundation. Here’s what developers need to know.

Git Flow branching diagram showing colorful merge lines with feature branches, hotfix workflows, and release commits on dark purple background for release-it plugin automation
April 28, 2025 • Frank Valcarcel

Git Flow Releases with ­­release-it

Learn how to build a custom release-it plugin that automates your entire Git Flow workflow: version bumps, branch merges, multi-branch pushes, and GitHub releases with a single command.

Let's work together

Tell us about your project and how Cuttlesoft can help. Schedule a consultation with one of our experts today.

Contact Us