What it is
RSpec is Ruby's behaviour-driven testing framework, with nested example groups, expressive matchers and built-in test doubles.
describe and context nest example groups; it declares an example. let defines lazily-evaluated helpers, and subject names the object under test.
Installation
gem 'rspec-rails', group: %i[development test]Getting started
The smallest useful thing you can do with it, and what each part means.
ruby
RSpec.describe BookService do
# Lazily evaluated, memoised per example.
let(:repository) { instance_double(BookRepository) }
subject(:service) { described_class.new(repository) }
describe '#title_for' do
context 'when the book exists' do
before do
allow(repository).to receive(:find).with(42)
.and_return(Book.new(title: 'Dune'))
end
it 'returns the title' do
expect(service.title_for(42)).to eq('Dune')
end
end
context 'when the book is missing' do
before { allow(repository).to receive(:find).and_return(nil) }
it 'raises NotFound' do
expect { service.title_for(99) }.to raise_error(BookService::NotFound)
end
end
end
endAdvanced usage
Where the library earns its place over a simpler alternative.
ruby
RSpec.shared_examples 'a soft-deletable record' do
it 'sets deleted_at instead of destroying' do
subject.soft_delete!
expect(subject.deleted_at).to be_present
expect(described_class.unscoped).to include(subject)
end
end
RSpec.describe Book do
it_behaves_like 'a soft-deletable record'
end
RSpec::Matchers.define :be_published_in do |year|
match { |book| book.year == year }
failure_message { |book| "expected #{year}, got #{book.year}" }
end
expect(book).to be_published_in(1965)
# Change matchers express intent better than before/after comparisons.
expect { service.create(params) }.to change(Book, :count).by(1)Errors and fixes
The failures you are most likely to hit, and what actually resolves them.
- does not implement: find
- instance_double is verifying — the method does not exist on the real class. Fix the name, or the class no longer has that API.
- Specs pass alone but fail together
- Leaked global state. Enable config.order = :random to surface order dependence early.
Best practices
- Use instance_double and class_double rather than plain double so stubs are verified against the real API.
- Keep let for setup that most examples need; too many lets make a spec hard to follow.
- One expectation per example where practical — failures then name the exact behaviour.
- Use the change matcher rather than manual before-and-after counting.
Background
Why it exists, and what it was reacting to.
RSpec popularised behaviour-driven development in Ruby. Its readable describe/context/it structure influenced testing frameworks across many other languages.
