Agent skill
RSpec Testing
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.
Install this agent skill to your Project
npx add-skill https://github.com/hoblin/claude-code-rails-skills/tree/main/rspec
SKILL.md
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) # eql? (type-sensitive)
expect(x).to be(y) # equal? (identity)
Truthiness
expect(x).to be_truthy # not nil or false
expect(x).to be_falsey # nil or false
expect(x).to be_nil
expect(x).to be true # exactly 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) # order-independent
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 # [].empty?
expect(user).to be_valid # user.valid?
expect(hash).to have_key(k) # hash.has_key?(k)
Test Doubles
Types
# Basic double (strict)
user = double("user", name: "Bob")
# Verifying doubles (recommended)
user = instance_double("User", name: "Bob") # validates instance methods
api = class_double("Api", fetch: data) # validates class methods
logger = object_double(Rails.logger) # validates object methods
# Spy (null object for after-the-fact verification)
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))
# Spy pattern (verify after action)
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_classinstead of hardcoding class name - Use
letfor 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 plaindouble - Name examples with verbs:
it "creates user"notit "should create user" - Keep examples focused on one behavior
- Use factories over fixtures for flexible test data
- Prefer
build_stubbedorbuildovercreatewhen database not needed
Don't
- Don't use instance variables (
@user) - useletfor type safety - Don't use
beforejust to triggerletevaluation - uselet!instead:rubyUse# BAD - before just to initialize let(:user) { create(:user) } before { user } # GOOD - let! for eager evaluation let!(:user) { create(:user) }beforefor side-effects likesign_in(user)ordriven_by(:rack_test) - Don't use
let!whenletsuffices (wastes resources) - Don't test Rails framework (validations work, focus on business logic)
- Don't stub the object under test
- Avoid
any_instance_of- prefer stubbingClassName.newto return a double (seereferences/mocks.md) - Don't use
receive_message_chain(violates Law of Demeter) - Don't write examples without descriptions
- Shared examples work best for testing concerns across including classes. For unique behaviors, prefer repetition over abstraction
Factory Bot
# Build strategies
user = build(:user) # In-memory, not persisted
user = create(:user) # Persisted to database
user = build_stubbed(:user) # Fake persisted (fastest)
attrs = attributes_for(:user) # Hash of attributes
# With traits and attributes
user = create(:user, :admin, :verified, name: "Bob")
# Lists
users = create_list(:user, 5, :admin)
Strategy Selection:
build_stubbed- Unit tests without database (fastest)build- Validation tests, method testscreate- Database queries, scopes, associationsattributes_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.
Additional Resources
Reference Files
For detailed patterns and complete API references, consult:
references/core.md- describe, it, hooks, let/subject, shared examples, anti-patternsreferences/factory_bot.md- Build strategies, traits, sequences, associations, callbacksreferences/matchers.md- All built-in matchers, composing matchers, custom matchersreferences/mocks.md- Test doubles, stubbing, spies, argument matchersreferences/rails.md- All Rails spec types, Rails matchers, request/system specs
Example Files
Ready-to-use code patterns in examples/:
examples/core/- Basic structure, hooks, let/subject, shared examples, configurationexamples/matchers/- Equality, collections, change, errors, predicates, custom matchersexamples/mocks/- Doubles, stubs, spies, argument matchers, any_instance, constantsexamples/rails/- Model, request, controller, system, job, mailer, routing specsexamples/factory_bot/- Build strategies, traits, associations, callbacks, transients
Running Specs
rspec # all specs
rspec spec/models # directory
rspec spec/user_spec.rb # file
rspec spec/user_spec.rb:23 # line
rspec --format doc # documentation format
rspec --only-failures # re-run failures
rspec --profile 10 # show slowest
Debugging
rspec --seed 12345 # reproduce random order
rspec --fail-fast # stop on first failure
rspec --backtrace # full backtrace
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
Draper Decorators
This skill should be used when the user asks to "create a decorator", "write a decorator", "move logic into decorator", "clean logic out of the view", "isn't it decorator logic", "test a decorator", or mentions Draper, keeping views clean, or representation logic in decorators. Should also be used when editing *_decorator.rb files, working in app/decorators/ directory, questioning where formatting methods belong (models vs decorators vs views), or discussing methods like full_name, formatted_*, display_* that don't belong in models. Provides guidance on Draper gem best practices for Rails applications.
activerecord
MCP Server (Ruby)
This skill should be used when the user asks to "create an MCP server", "build MCP tools", "define MCP prompts", "register MCP resources", "implement Model Context Protocol", or mentions the mcp gem, MCP::Server, MCP::Tool, JSON-RPC transport, stdio transport, or streamable HTTP transport. Should also be used when editing MCP server files, working with tool/prompt/resource definitions, or discussing LLM tool integrations in Ruby.
verl-rl-training
Provides guidance for training LLMs with reinforcement learning using verl (Volcano Engine RL). Use when implementing RLHF, GRPO, PPO, or other RL algorithms for LLM post-training at scale with flexible infrastructure backends.
openrlhf-training
High-performance RLHF framework with Ray+vLLM acceleration. Use for PPO, GRPO, RLOO, DPO training of large models (7B-70B+). Built on Ray, vLLM, ZeRO-3. 2× faster than DeepSpeedChat with distributed architecture and GPU resource sharing.
gguf-quantization
GGUF format and llama.cpp quantization for efficient CPU/GPU inference. Use when deploying models on consumer hardware, Apple Silicon, or when needing flexible quantization from 2-8 bit without GPU requirements.
Didn't find tool you were looking for?