RSpec Conventions (Rootstrap)
Apply these whenever producing or modifying RSpec test code. Full guide: https://github.com/rootstrap/tech-guides/blob/master/ruby/rspec/style_guide.md
Complements ruby-conventions — language-level Ruby style still applies.
Structure & Formatting
- No blank line directly after
describe/context/featureopening. - Leave one blank line after
let/subjectgroups and afterbefore/afterblocks. - Group
letandsubjecttogether; separate them frombefore/afterwith a blank line. - One blank line around each
itblock. - Use
before(notbefore(:each));before(:all)should be rare and explicit.
describe & context
describe '#method'for instance methods,describe '.method'for class methods.contextdescriptions must start withwhenorwith, forming a grammatical sentence.context 'when the display name is not present'- Every
contextshould have a matching opposite/negative case — a lone context is a smell. - Don't end
itdescriptions with a conditional ("...if X") — wrap in acontextinstead.
it / Examples
- Never start
itwith "should" / "should not". State behavior directly.it 'returns the summary' # not: 'should return the summary' - One expectation per
itblock (split multi-assertion examples). - Avoid generating examples via iterators; use shared examples instead.
shared_examples 'responds successfully' do it 'returns 200' do ... end end it_behaves_like 'responds successfully'
let & subject
- Prefer
letoverbefore { @x = ... }for test data (lazy, no instance vars). letis lazy (evaluated on first reference) and cached within a single example, not across examples. Uselet!when you need eager evaluation before each example.- Use
letfor values shared across several (not necessarily all)its in a context; avoid overuse — it hurts readability. - Use
subjectwhen describing a single primary object.subject { create(:article) }
Matchers
- Use RSpec magic matchers for predicate methods:
expect(subject).to be_published(callspublished?). - Prefer
changematchers over counting state before/after.expect { article.publish }.to change(Article, :count).by(1) - Avoid asserting incidental state (e.g.
Article.count == 2) — test the delta or direct effect.
Factories & Fixtures
- Use FactoryBot (
create(:article)); never Rails fixtures. - Avoid
ModelName.createin integration specs — reach for the factory. - Use Faker for fake data; Database Cleaner keeps state isolated.
Mocking / Stubbing / Doubles
- Use mocks/stubs sparingly — favor them in isolated/behavioral specs, not integration specs.
- Only stub against small, stable, well-known APIs.
- Avoid
allow_any_instance_of; it stubs every instance of the class and can hide real interaction bugs. Prefer stubbing or mocking the specific instance used by the example. - Never stub a method whose return you're asserting on — produces false positives.
# Bad: stubs nil? itself allow(summary).to receive(:nil?).and_return(true) # Good: stub the upstream method, let the code run allow(subject).to receive(:summary).and_return(nil) - Use Webmock/VCR for HTTP; never hit real APIs in tests.
Shared Examples
- Extract shared examples when duplication is real and clarifies intent — don't DRY prematurely.
- Duplication inside specs is acceptable, even preferred, if it aids readability.
Spec Types (quick guidance)
- Model/service/job/mailer specs — unit tests of public methods and callbacks; don't test private methods.
- Feature specs — full UI flow via Capybara; only enable JS driver when required.
- Controller specs — for non-API controllers and edge cases faster than feature specs.
- Request specs (
type: :request) — preferred over controller specs for APIs; exercises routes and real responses. - Prefer
describe ... dostyle over Capybara'sfeature/scenarioDSL for consistency.