Reusing Behavior with RSpec Shared Examples: A Practical Guide
Learn how to define shared_examples in RSpec, parameterize them with block arguments, and apply the same behavior to different object types while keeping your test suite DRY and readable.
31 Jan 2026, 17:26 UTC

Problem: Repeating the Same Expectations Across Specs
When you test similar behavior on different objects—for example, checking that a collection responds to #each and yields items in order—you often end up copying and pasting the same it blocks. This duplication makes the suite harder to maintain and increases the chance that a change in one place is forgotten elsewhere.
Thesis: Shared Examples Let You Define Behavior Once and Reuse It Safely
RSpec’s shared_examples feature lets you extract a group of examples into a named block and include it wherever needed with it_behaves_like or include_examples. The block can receive arguments, so the same behavior can be adapted to different contexts without losing clarity.
Defining a Shared Example Group
Start by declaring the shared behavior. The block receives parameters that you pass when you include it.
# spec/shared/paginatable_spec.rb
RSpec.shared_examples 'a paginatable collection' do |collection|
it 'responds to #page' do
expect(collection).to respond_to(:page)
end
it 'returns the first page when #page(1) is called' do
expect(collection.page(1)).to eq(collection.first(2))
end
it 'returns an empty array when asked for a page beyond the size' do
expect(collection.page(10)).to eq([])
end
end
In this example, collection is a block argument that will be supplied by the including group.
Including the Shared Behavior with Arguments
Use it_behaves_like (or include_examples) and provide the argument after the shared‑example name.
# spec/lib/array_paginatable_spec.rb
require 'rails_helper'
RSpec.describe Array do
let(:data) { [1, 2, 3, 4, 5] }
it_behaves_like 'a paginatable collection', data
end
# spec/lib/relation_paginatable_spec.rb
require 'rails_helper'
RSpec.describe 'ActiveRecord::Relation (simulated)' do
let(:relation) { double('relation', page: ->(n) { [] }) }
it_behaves_like 'a paginatable collection', relation
end
Each spec file now inherits the three expectations, but the actual object under test is supplied via the argument.
Worked Example: Verifying the Shared Behavior
- Create a new directory for the demo and initialize a Gemfile with
gem 'rspec'. - Run
bundle install. - Create the
spec/shared/paginatable_spec.rbfile with the shared block shown above. - Create the two inclusion files (
spec/lib/array_paginatable_spec.rbandspec/lib/relation_paginatable_spec.rb) as shown. - Execute
bundle exec rspec --format documentation.
You should see the shared examples appear under each describing block, indicating that the same three expectations were executed for both the Array and the simulated Relation. If you change an expectation inside the shared block (for instance, expecting nil instead of an empty array), both test groups will reflect the change, confirming that the behavior is truly shared.
Trade‑off: Potential Loss of Local Context
While shared examples reduce duplication, they can make it harder to grasp what a specific spec is verifying without jumping to the shared definition. Overuse may lead to a test suite where the intent of individual it blocks is obscured.
To mitigate this:
- Keep shared example names descriptive (
a paginatable collectionrather thanshared stuff). - Limit the amount of logic inside the shared block; prefer simple expectations that are truly identical across contexts.
- When a shared block grows large, consider splitting it into smaller, more focused groups.
Actionable Closing
Start by identifying a repetitive pattern in your current specs—perhaps a set of matcher calls that appear on multiple models or value objects. Extract that pattern into a shared_examples block, pass any needed context via block arguments, and replace the duplicated code with it_behaves_like. Run the suite to confirm nothing breaks, then revisit the shared definition periodically to ensure it remains clear and focused.
By using shared examples judiciously, you keep your test suite DRY while preserving readability—a practical win for both maintenance and confidence in your code.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.