| name | rspec |
| version | 1.1.2 |
| description | This skill should be used when the user asks to "write specs", "create spec", "add RSpec tests", "fix failing spec", or mentions RSpec, describe blocks, it blocks, expect syntax, test doubles, or matchers. Should also be used when editing *_spec.rb files, working in spec/ directory, planning implementation phases that include tests (TDD/RGRC workflow), writing Testing Strategy or Success Criteria sections, discussing unit or integration tests, or reviewing spec output and test failures. Comprehensive RSpec and FactoryBot reference with best practices, ready-to-use patterns, and examples. |
RSpec Testing
This skill provides comprehensive guidance for writing effective RSpec tests in Ruby and Rails applications. Use for writing new specs, fixing failing tests, understanding matchers, using test doubles, and following RSpec best practices.
Quick Reference
Basic Structure
RSpec.describe Order do
subject(:order) { described_class.new(items) }
let(:items) { [item1, item2] }
let(:item1) { double("item", price: 10) }
let(:item2) { double("item", price: 20) }
describe "#total" do
it "sums item prices" do
expect(order.total).to eq(30)
end
end
context "with discount" do
let(:order) { described_class.new(items, discount: 5) }
it "applies discount" do
expect(order.total).to eq(25)
end
end
end
Key Concepts
| Concept | Purpose |
|---|
describe / context | Group related examples |
it / specify | Define individual test cases |
let | Lazy-evaluated, memoized helper |
let! | Eager-evaluated helper (runs before each example) |
subject | Primary object under test |
before / after | Setup and teardown hooks |
expect | Make assertions |
Writing Good Specs
Use Named Subject for Method Tests
describe "#calculate_total" do
subject(:total) { order.calculate_total }
it "returns sum of items" do
expect(total).to eq(100)
end
end
Context Blocks for Different States
describe "#withdraw" do
context "with sufficient funds" do
let(:account) { build(:account, balance: 100) }
it "reduces balance" do
expect { account.withdraw(50) }.to change(account, :balance).by(-50)
end
end
context "with insufficient funds" do
let(:account) { build(:account, balance: 10) }
it "raises error" do
expect { account.withdraw(50) }.to raise_error(InsufficientFunds)
end
end
end
Common Matchers
Equality
expect(x).to eq(y)
expect(x).to eql(y)
expect(x).to be(y)
Truthiness
expect(x).to be_truthy
expect(x).to be_falsey
expect(x).to be_nil
expect(x).to be true
Comparisons
expect(x).to be > 3
expect(x).to be_between(1, 10).inclusive
expect(x).to be_within(0.1).of(3.14)
Collections
expect(arr).to include(1, 2)
expect(arr).to contain_exactly(3, 2, 1)
expect(arr).to all(be_positive)
expect(str).to start_with("hello")
expect(hash).to have_key(:name)
Changes
expect { x += 1 }.to change { x }.by(1)
expect { x += 1 }.to change { x }.from(0).to(1)
expect { user.save }.to change(User, :count).by(1)
Errors
expect { raise "boom" }.to raise_error
expect { raise ArgumentError, "bad" }.to raise_error(ArgumentError, /bad/)
Predicates (Dynamic)
expect([]).to be_empty
expect(user).to be_valid
expect(hash).to have_key(k)
Test Doubles
Types
user = double("user", name: "Bob")
user = instance_double("User", name: "Bob")
api = class_double("Api", fetch: data)
logger = object_double(Rails.logger)
notifier = spy("notifier")
Stubbing
allow(user).to receive(:name).and_return("Bob")
allow(Api).to receive(:fetch).and_return(data)
allow(obj).to receive(:method) { computed_value }
Expectations
expect(user).to receive(:save).and_return(true)
expect(Api).to receive(:post).with(hash_including(id: 1))
notifier = spy("notifier")
service.call(notifier)
expect(notifier).to have_received(:notify).with("done")
Argument Matchers
expect(obj).to receive(:call).with(anything)
expect(obj).to receive(:call).with(kind_of(Integer))
expect(obj).to receive(:call).with(hash_including(a: 1))
expect(obj).to receive(:call).with(array_including(1, 2))
Rails Specs
| Spec Type | Use For | Key Helpers |
|---|
type: :model | Business logic, scopes, validations | build, create, associations |
type: :request | Controller actions (preferred) | get, post, response, have_http_status |
type: :system | Browser/UI testing | visit, fill_in, click_button, have_text |
type: :job | Background jobs | have_enqueued_job, perform_now |
type: :mailer | Email delivery | have_enqueued_mail, deliver_now |
type: :routing | Route resolution | route_to, be_routable |
See examples/rails/ for complete spec templates.
Best Practices
Do
- Use
described_class instead of hardcoding class name
- Use
let for test data, let! when database records must exist before test
- Use named subject when referencing in tests:
subject(:user) { ... }
- Use context blocks to organize different scenarios
- Use verifying doubles (
instance_double) over plain double
- Name examples with verbs:
it "creates user" not it "should create user"
- Keep examples focused on one behavior
- Use factories over fixtures for flexible test data
- Prefer
build_stubbed or build over create when database not needed
Don't
Factory Bot
user = build(:user)
user = create(:user)
user = build_stubbed(:user)
attrs = attributes_for(:user)
user = create(:user, :admin, :verified, name: "Bob")
users = create_list(:user, 5, :admin)
Strategy Selection:
build_stubbed - Unit tests without database (fastest)
build - Validation tests, method tests
create - Database queries, scopes, associations
attributes_for - Controller params
See references/factory_bot.md for traits, sequences, associations, and callbacks.
Configuration
Essential settings for spec_helper.rb and rails_helper.rb:
| Setting | Purpose |
|---|
verify_partial_doubles = true | Validates stubbed methods exist |
filter_run_when_matching :focus | Run only focused specs (fit, fdescribe) |
order = :random | Randomize spec order to catch dependencies |
use_transactional_fixtures = true | Rollback database after each spec |
infer_spec_type_from_file_location! | Auto-detect spec type from path |
See examples/core/configuration.rb for complete setup.
Before You Write
What are you about to do?
├── Creating or modifying a factory?
│ └── Read `references/factory_bot.md`
│
├── Writing a new spec file?
│ ├── Model/service/PORO → Read `references/core.md`
│ ├── Request/controller → Read `references/rails.md`
│ └── System/feature/job/mailer → Read `references/rails.md`
│
├── Using test doubles, stubs, or mocks?
│ └── Read `references/mocks.md`
│
├── Writing custom or complex matchers?
│ └── Read `references/matchers.md`
│
└── Fixing a failing spec?
├── Factory-related error → Read `references/factory_bot.md`
├── Mock/stub error → Read `references/mocks.md`
├── Matcher error → Read `references/matchers.md`
└── Rails-specific error → Read `references/rails.md`
Need code examples?
├── Basic spec structure, hooks, shared examples
│ └── See `examples/core/`
│
├── Matcher usage patterns
│ └── See `examples/matchers/`
│
├── Test doubles and stubbing
│ └── See `examples/mocks/`
│
├── Rails spec templates (model, request, system, job, mailer)
│ └── See `examples/rails/`
│
└── Factory definitions with traits and associations
└── See `examples/factory_bot/`
Running Specs
rspec
rspec spec/models
rspec spec/user_spec.rb
rspec spec/user_spec.rb:23
rspec --format doc
rspec --only-failures
rspec --profile 10
Debugging
rspec --seed 12345
rspec --fail-fast
rspec --backtrace