# Rspec Core

> 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.

- Skill: `bastos/rspec-core` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add bastos/rspec-core`
- Raw SKILL.md: https://api.skillmd.com/api/skills/bastos/rspec-core/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: bastos (https://skillmd.com/u/bastos)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/bastos/rspec-core

---


# 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:

```ruby
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...":

```ruby
it "returns the user's email" do
  expect(user.email).to eq("test@example.com")
end

# For complex descriptions, use specify
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:

```ruby
RSpec.describe User do
  subject { User.new(name: "John") }

  it { is_expected.to be_valid }

  # Named subject for clarity
  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:

```ruby
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

```ruby
let(:user) { User.create!(name: "John") }    # Created only if referenced
let!(:admin) { User.create!(admin: true) }   # Always created before each test
```

**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:

```ruby
RSpec.describe User do
  before(:each) do    # or just `before do`
    @user = User.new
  end

  after(:each) do
    # Cleanup after each example
  end

  before(:all) do     # or before(:context)
    # Run once before all examples in this group
    # Be careful: shared state between examples
  end

  after(:all) do
    # Run once after all examples
  end
end
```

**Hook execution order:**
1. `before(:all)` - once per describe/context block
2. `before(:each)` - before every example
3. The example runs
4. `after(:each)` - after every example
5. `after(:all)` - once after all examples

### around

Wrap examples with custom logic:

```ruby
around(:each) do |example|
  Timecop.freeze(Time.now) do
    example.run
  end
end

# Useful for database transactions
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:

```ruby
it "sends welcome email" do
  pending "email service not implemented"
  expect(user.send_welcome_email).to be_truthy
end

# Or mark entire example as pending
pending "calculates complex tax" do
  expect(order.tax).to eq(15.00)
end
```

### skip

Skip examples entirely:

```ruby
it "requires external service", skip: "API not available in test" do
  # This won't run
end

# Conditional skip
it "runs on CI only", skip: !ENV["CI"] do
  # Skipped locally
end

# Skip with xit
xit "not implemented yet" do
  # Skipped
end
```

## Metadata and Filtering

Add metadata to examples and filter runs:

```ruby
RSpec.describe User, type: :model do
  it "validates email", :slow do
    # Tagged with :slow
  end

  it "processes payment", :integration, timeout: 30 do
    # Multiple tags
  end
end

# Run filtered specs:
# rspec --tag slow
# rspec --tag ~slow  (exclude slow)
# rspec --tag type:model
```

### Focus

Temporarily run only specific examples:

```ruby
fit "only this test runs" do     # focus + it
  expect(true).to be true
end

fdescribe User do                 # focus + describe
  # Only examples in this block run
end

fcontext "when focused" do        # focus + context
end
```

**Important:** Remove focus tags before committing. Configure CI to fail on focus:

```ruby
# spec/spec_helper.rb
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

```ruby
# spec/spec_helper.rb
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

```ruby
# Good - describes behavior
it "returns user's full name" do
  expect(user.full_name).to eq("John Doe")
end

# Avoid - describes implementation
it "concatenates first_name and last_name with space" do
  expect(user.full_name).to eq("John Doe")
end
```

### Use Descriptive Context Names

```ruby
# Good
context "when user is an admin"
context "with valid attributes"
context "without a password"

# Avoid
context "admin case"
context "valid"
context "test 1"
```

### Prefer let Over Instance Variables

```ruby
# Good
let(:user) { create(:user) }

# Avoid
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

