| name | rspec-core |
| description | This skill should be used when the user asks to "write an RSpec test", "create a spec", "use describe blocks", "set up let variables", "use before hooks", "understand RSpec structure", mentions "subject", "context", "it blocks", or needs guidance on RSpec 3.x fundamentals and test structure. |
| version | 1.0.0 |
RSpec Core Fundamentals
RSpec is a behavior-driven development (BDD) testing framework for Ruby. This skill covers the core DSL for structuring and writing specs.
Example Group Structure
describe and context
Use describe for the thing being tested (class, method, behavior). Use context for different scenarios or states:
RSpec.describe User do
describe "#full_name" do
context "when user has both names" do
it "returns first and last name" do
user = User.new(first_name: "John", last_name: "Doe")
expect(user.full_name).to eq("John Doe")
end
end
context "when user has no last name" do
it "returns only first name" do
user = User.new(first_name: "John", last_name: nil)
expect(user.full_name).to eq("John")
end
end
end
end
Naming conventions:
describe with class name: RSpec.describe User
describe with method: describe "#instance_method" or describe ".class_method"
context starts with "when", "with", "without", "given": context "when logged in"
it and specify
Use it for individual test cases. The description should complete the sentence "it...":
it "returns the user's email" do
expect(user.email).to eq("test@example.com")
end
specify "the total includes tax and shipping" do
expect(order.total).to eq(110.00)
end
One expectation per example (generally): Each it block should test one behavior. Multiple expectations are acceptable when testing related aspects of a single behavior.
Subject and Let
subject
Defines the primary object under test:
RSpec.describe User do
subject { User.new(name: "John") }
it { is_expected.to be_valid }
subject(:user) { User.new(name: "John") }
it "has the correct name" do
expect(user.name).to eq("John")
end
end
let and let!
Use let for lazy-evaluated memoized helpers:
RSpec.describe Order do
let(:user) { User.create!(name: "John") }
let(:product) { Product.create!(name: "Widget", price: 10) }
let(:order) { Order.new(user: user, product: product) }
it "calculates total correctly" do
expect(order.total).to eq(10)
end
end
let vs let!:
let - Lazy evaluation, created when first referenced
let! - Eager evaluation, created before each example
let(:user) { User.create!(name: "John") }
let!(:admin) { User.create!(admin: true) }
When to use let!:
- Database records that must exist for callbacks/associations
- Setup that has side effects needed by other code
- When order of creation matters
Hooks
before and after
Execute code before or after examples:
RSpec.describe User do
before(:each) do
@user = User.new
end
after(:each) do
end
before(:all) do
end
after(:all) do
end
end
Hook execution order:
before(:all) - once per describe/context block
before(:each) - before every example
- The example runs
after(:each) - after every example
after(:all) - once after all examples
around
Wrap examples with custom logic:
around(:each) do |example|
Timecop.freeze(Time.now) do
example.run
end
end
around(:each) do |example|
ActiveRecord::Base.transaction do
example.run
raise ActiveRecord::Rollback
end
end
Pending and Skipping
pending
Mark examples as not yet implemented or temporarily broken:
it "sends welcome email" do
pending "email service not implemented"
expect(user.send_welcome_email).to be_truthy
end
pending "calculates complex tax" do
expect(order.tax).to eq(15.00)
end
skip
Skip examples entirely:
it "requires external service", skip: "API not available in test" do
end
it "runs on CI only", skip: !ENV["CI"] do
end
xit "not implemented yet" do
end
Metadata and Filtering
Add metadata to examples and filter runs:
RSpec.describe User, type: :model do
it "validates email", :slow do
end
it "processes payment", :integration, timeout: 30 do
end
end
Focus
Temporarily run only specific examples:
fit "only this test runs" do
expect(true).to be true
end
fdescribe User do
end
fcontext "when focused" do
end
Important: Remove focus tags before committing. Configure CI to fail on focus:
RSpec.configure do |config|
config.filter_run_when_matching :focus
config.run_all_when_everything_filtered = true
end
Configuration
spec_helper.rb vs rails_helper.rb
spec_helper.rb - Pure RSpec configuration, no Rails
rails_helper.rb - Rails-specific setup, requires spec_helper
RSpec.configure do |config|
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
config.shared_context_metadata_behavior = :apply_to_host_groups
config.filter_run_when_matching :focus
config.example_status_persistence_file_path = "spec/examples.txt"
config.disable_monkey_patching!
config.order = :random
Kernel.srand config.seed
end
Best Practices
Describe What, Not How
it "returns user's full name" do
expect(user.full_name).to eq("John Doe")
end
it "concatenates first_name and last_name with space" do
expect(user.full_name).to eq("John Doe")
end
Use Descriptive Context Names
context "when user is an admin"
context "with valid attributes"
context "without a password"
context "admin case"
context "valid"
context "test 1"
Prefer let Over Instance Variables
let(:user) { create(:user) }
before { @user = create(:user) }
Keep Examples Independent
Each example should be able to run in isolation. Avoid dependencies between examples.
Additional Resources
For advanced patterns and detailed examples:
references/hooks-deep-dive.md - Comprehensive hook patterns and execution order