Marketplace

rspec-coder

This skill guides writing comprehensive RSpec tests for Ruby and Rails applications. Use when creating spec files, writing test cases, or testing new features. Covers RSpec syntax, describe/context organization, subject/let patterns, fixtures, mocking with allow/expect, and shoulda matchers.

allowed_tools: Read, Write, Edit, MultiEdit, Grep, Glob, Bash, WebSearch

$ 安裝

git clone https://github.com/majesticlabs-dev/majestic-marketplace /tmp/majestic-marketplace && cp -r /tmp/majestic-marketplace/plugins/majestic-rails/skills/rspec-coder ~/.claude/skills/majestic-marketplace

// tip: Run this command in your terminal to install the skill


name: rspec-coder description: This skill guides writing comprehensive RSpec tests for Ruby and Rails applications. Use when creating spec files, writing test cases, or testing new features. Covers RSpec syntax, describe/context organization, subject/let patterns, fixtures, mocking with allow/expect, and shoulda matchers. allowed-tools: Read, Write, Edit, MultiEdit, Grep, Glob, Bash, WebSearch

RSpec Coder

Core Philosophy

  • AAA Pattern: Arrange-Act-Assert structure for clarity
  • Behavior over Implementation: Test what code does, not how
  • Isolation: Tests should be independent
  • Descriptive Names: Blocks should clearly explain behavior
  • Coverage: Test happy paths AND edge cases
  • Fast Tests: Minimize database operations
  • Fixtures: Use fixtures for common data setup
  • Shoulda Matchers: Use for validations and associations

Critical Conventions

❌ Don't Add require 'rails_helper'

RSpec imports via .rspec config. Adding manually is redundant.

# ✅ GOOD - no require needed
RSpec.describe User do
  # ...
end

❌ Don't Add Redundant Spec Type

RSpec infers type from file location automatically.

# ✅ GOOD - type inferred from spec/models/ location
RSpec.describe User do
  # ...
end

✅ Use Namespace WITHOUT Leading ::

# ✅ GOOD - no leading double colons
RSpec.describe DynamicsGp::ERPSynchronizer do
  # ...
end

Test Organization

File Structure

spec/
├── models/           # Model unit tests
├── services/         # Service object tests
├── controllers/      # Controller tests
├── requests/         # Request specs (API testing)
├── mailers/          # Mailer tests
├── jobs/             # Background job tests
├── fixtures/         # Test data
├── support/          # Helper modules and shared examples
└── rails_helper.rb   # Rails-specific configuration

Using describe and context

BlockPurposeExample
describeGroups by method/classdescribe "#process"
contextGroups by conditioncontext "when user is admin"
RSpec.describe OrderProcessor do
  describe "#process" do
    context "with valid payment" do
      # success tests
    end

    context "with invalid payment" do
      # failure tests
    end
  end
end

Subject and Let

See resources/patterns.md for detailed examples.

PatternUse Case
subject(:name) { ... }Primary object/method under test
let(:name) { ... }Lazy-evaluated, memoized data
let!(:name) { ... }Eager evaluation (before each test)
RSpec.describe User do
  describe "#full_name" do
    subject(:full_name) { user.full_name }
    let(:user) { users(:alice) }

    it { is_expected.to eq("Alice Smith") }
  end
end

Fixtures

See resources/patterns.md for detailed examples.

# spec/fixtures/users.yml
alice:
  name: Alice Smith
  email: alice@example.com
  admin: false
RSpec.describe User do
  fixtures :users

  it "validates email" do
    expect(users(:alice)).to be_valid
  end
end

Mocking and Stubbing

See resources/patterns.md for detailed examples.

MethodPurpose
allow(obj).to receive(:method)Stub return value
expect(obj).to receive(:method)Verify call happens
# Stubbing external service
allow(PaymentGateway).to receive(:charge).and_return(true)

# Verifying method called
expect(UserMailer).to receive(:welcome_email).with(user)

Matchers Quick Reference

See resources/matchers.md for complete reference.

Essential Matchers

# Equality
expect(value).to eq(expected)

# Truthiness
expect(obj).to be_valid
expect(obj).to be_truthy

# Change
expect { action }.to change { obj.status }.to("completed")
expect { action }.to change(Model, :count).by(1)

# Errors
expect { action }.to raise_error(SomeError)

# Collections
expect(array).to include(item)
expect(array).to be_empty

Shoulda Matchers

# Validations
it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_uniqueness_of(:email) }

# Associations
it { is_expected.to have_many(:posts) }
it { is_expected.to belong_to(:account) }

AAA Pattern

Structure all tests as Arrange-Act-Assert:

describe "#process_refund" do
  subject(:process_refund) { processor.process_refund }

  let(:order) { orders(:completed_order) }
  let(:processor) { described_class.new(order) }

  it "updates order status" do
    process_refund  # Act
    expect(order.reload.status).to eq("refunded")  # Assert
  end

  it "credits user account" do
    expect { process_refund }  # Act
      .to change { order.user.reload.account_balance }  # Assert
      .by(order.total)
  end
end

Test Coverage Standards

What to Test

TypeTest For
ModelsValidations, associations, scopes, callbacks, methods
ServicesHappy path, sad path, edge cases, external integrations
ControllersStatus codes, response formats, auth, redirects
JobsExecution, retry logic, error handling, idempotency

Coverage Example

RSpec.describe User do
  fixtures :users

  describe "validations" do
    subject(:user) { users(:valid_user) }

    it { is_expected.to validate_presence_of(:name) }
    it { is_expected.to validate_presence_of(:email) }
    it { is_expected.to validate_uniqueness_of(:email).case_insensitive }
  end

  describe "associations" do
    it { is_expected.to have_many(:posts).dependent(:destroy) }
  end

  describe "#full_name" do
    subject(:full_name) { user.full_name }
    let(:user) { User.new(first_name: "Alice", last_name: "Smith") }

    it { is_expected.to eq("Alice Smith") }

    context "when last name is missing" do
      let(:user) { User.new(first_name: "Alice") }
      it { is_expected.to eq("Alice") }
    end
  end
end

Anti-Patterns

See resources/anti-patterns.md for detailed examples.

Anti-PatternWhy Bad
require 'rails_helper'Redundant, loaded via .rspec
type: :modelRedundant, inferred from location
Leading :: in namespaceViolates RuboCop style
Empty test bodiesFalse confidence
Testing private methodsCouples to implementation
Not using fixturesSlow tests
Not using shouldaVerbose validation tests

Best Practices Checklist

Critical Conventions:

  • NOT adding require 'rails_helper'
  • NOT adding redundant spec type
  • Using namespace WITHOUT leading ::

Test Organization:

  • describe for methods/classes
  • context for conditions
  • Max 3 levels nesting

Test Data:

  • Using fixtures (not factories)
  • Using let for lazy data
  • Using subject for method under test

Assertions:

  • Shoulda matchers for validations
  • Shoulda matchers for associations
  • change matcher for state changes

Coverage:

  • Happy path tested
  • Sad path tested
  • Edge cases covered

Quick Reference

# Minimal spec file
RSpec.describe User do
  fixtures :users

  describe "#full_name" do
    subject(:full_name) { user.full_name }
    let(:user) { users(:alice) }

    it { is_expected.to eq("Alice Smith") }
  end

  describe "validations" do
    subject(:user) { users(:alice) }

    it { is_expected.to validate_presence_of(:name) }
    it { is_expected.to have_many(:posts) }
  end
end