ruby-testing

RSpec testing patterns for Ruby and Rails — factories, mocks, request specs, feature specs, VCR, and SimpleCov coverage.

8 stars

Best use case

ruby-testing is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

RSpec testing patterns for Ruby and Rails — factories, mocks, request specs, feature specs, VCR, and SimpleCov coverage.

Teams using ruby-testing should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/ruby-testing/SKILL.md --create-dirs "https://raw.githubusercontent.com/marvinrichter/clarc/main/skills/ruby-testing/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/ruby-testing/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How ruby-testing Compares

Feature / Agentruby-testingStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

RSpec testing patterns for Ruby and Rails — factories, mocks, request specs, feature specs, VCR, and SimpleCov coverage.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Ruby Testing

## When to Activate

Use this skill when:
- Writing tests for Ruby or Rails applications
- Setting up RSpec in a new project
- Writing model, request, or feature specs
- Using FactoryBot for test data
- Mocking external services in tests
- Setting up code coverage with SimpleCov
- Debugging flaky tests or test isolation issues
- Writing property-based tests for Ruby code

## RSpec Setup

```ruby
# Gemfile (test group)
group :test do
  gem 'rspec-rails'
  gem 'factory_bot_rails'
  gem 'shoulda-matchers'
  gem 'faker'
  gem 'database_cleaner-active_record'
  gem 'vcr'
  gem 'webmock'
  gem 'simplecov', require: false
  gem 'capybara'       # for feature specs
  gem 'selenium-webdriver'
end
```

```ruby
# spec/spec_helper.rb
require 'simplecov'
SimpleCov.start 'rails' do
  minimum_coverage 80
  add_filter '/spec/'
  add_filter '/config/'
end
```

## Unit Tests (Models)

```ruby
RSpec.describe User, type: :model do
  # Shoulda-Matchers for associations and validations
  it { is_expected.to validate_presence_of(:email) }
  it { is_expected.to validate_uniqueness_of(:email).case_insensitive }
  it { is_expected.to have_many(:posts).dependent(:destroy) }
  it { is_expected.to belong_to(:organization) }

  describe '#full_name' do
    subject(:user) { build(:user, first_name: 'Jane', last_name: 'Doe') }
    it { expect(user.full_name).to eq('Jane Doe') }
  end

  describe '.active' do
    it 'returns only active users' do
      active = create(:user, active: true)
      _inactive = create(:user, active: false)
      expect(User.active).to contain_exactly(active)
    end
  end
end
```

## Service Object Tests

```ruby
RSpec.describe UserRegistrationService do
  describe '#call' do
    subject(:service) { described_class.new(params) }

    context 'with valid params' do
      let(:params) { attributes_for(:user) }

      it 'creates a user' do
        expect { service.call }.to change(User, :count).by(1)
      end

      it 'sends welcome email' do
        expect { service.call }.to have_enqueued_mail(UserMailer, :welcome)
      end
    end

    context 'with duplicate email' do
      let!(:existing) { create(:user) }
      let(:params) { { email: existing.email, password: 'password' } }

      it 'raises ValidationError' do
        expect { service.call }.to raise_error(ValidationError, /email.*taken/i)
      end
    end
  end
end
```

## Request Specs (API)

```ruby
RSpec.describe 'POST /api/v1/users', type: :request do
  let(:valid_params) { { user: attributes_for(:user) } }
  let(:headers) { { 'Accept' => 'application/json', 'Content-Type' => 'application/json' } }

  context 'with valid params' do
    it 'returns 201 Created' do
      post '/api/v1/users', params: valid_params.to_json, headers: headers
      expect(response).to have_http_status(:created)
    end

    it 'returns the created user' do
      post '/api/v1/users', params: valid_params.to_json, headers: headers
      expect(JSON.parse(response.body)).to include('email' => valid_params[:user][:email])
    end
  end

  context 'with invalid email' do
    it 'returns 422 Unprocessable Entity' do
      post '/api/v1/users', params: { user: { email: 'bad', password: 'pw' } }.to_json, headers: headers
      expect(response).to have_http_status(:unprocessable_entity)
    end
  end
end
```

## FactoryBot Best Practices

```ruby
# spec/factories/users.rb
FactoryBot.define do
  factory :user do
    sequence(:email) { |n| "user#{n}@example.com" }
    password { 'SecurePass123!' }
    first_name { Faker::Name.first_name }
    last_name  { Faker::Name.last_name }
    active { true }

    trait :admin do
      role { :admin }
    end

    trait :inactive do
      active { false }
    end

    trait :with_posts do
      after(:create) do |user|
        create_list(:post, 3, author: user)
      end
    end
  end
end
```

## VCR for External Services

```ruby
# spec/support/vcr.rb
VCR.configure do |c|
  c.cassette_library_dir = 'spec/cassettes'
  c.hook_into :webmock
  c.configure_rspec_metadata!
  c.filter_sensitive_data('<STRIPE_KEY>') { ENV['STRIPE_API_KEY'] }
end

# Usage in spec:
RSpec.describe StripeCheckoutService, :vcr do
  it 'creates a payment intent' do
    result = described_class.new(amount: 1000).call
    expect(result.status).to eq('requires_payment_method')
  end
end
```

## Test Doubles and Mocks

```ruby
RSpec.describe NotificationService do
  let(:mailer) { instance_double(UserMailer, deliver_later: true) }

  before do
    allow(UserMailer).to receive(:notification).and_return(mailer)
  end

  it 'sends notification' do
    described_class.new(user).notify
    expect(UserMailer).to have_received(:notification).with(user)
    expect(mailer).to have_received(:deliver_later)
  end
end
```

## Anti-Patterns

```ruby
# WRONG: Testing implementation details
it 'calls save! on the model' do
  expect(@user).to receive(:save!)
  service.call
end

# CORRECT: Test behavior and outcomes
it 'persists the user' do
  expect { service.call }.to change(User, :count).by(1)
end

# WRONG: Shared state between tests
before(:all) do
  @user = create(:user)  # Shared state causes test pollution
end

# CORRECT: Isolated per-test data
let(:user) { create(:user) }

# WRONG: Bypassing database cleaner
after(:each) { User.delete_all }

# CORRECT: Configure DatabaseCleaner properly in spec_helper
```

## Reference

- See `ruby-patterns` skill for service object and query object patterns
- See `rules/ruby/testing.md` for project-level testing standards

Related Skills

visual-testing

8
from marvinrichter/clarc

Visual Regression Testing: tool comparison (Chromatic/Percy/Playwright screenshots/BackstopJS), pixel-diff vs AI-based comparison, baseline management, flakiness strategies (masks, tolerances, waitForLoadState), CI integration with GitHub Actions, and Storybook integration.

typescript-testing

8
from marvinrichter/clarc

TypeScript testing patterns: Vitest for unit/integration, Playwright for E2E, MSW for API mocking, Testing Library for React components. Core TDD methodology for TypeScript/JavaScript projects.

swift-testing

8
from marvinrichter/clarc

Swift testing patterns: Swift Testing framework (Swift 6+), XCTest for UI tests, async/await test cases, actor testing, Combine testing, and XCUITest for UI automation. TDD for Swift/SwiftUI.

swift-protocol-di-testing

8
from marvinrichter/clarc

Protocol-based dependency injection for testable Swift code — mock file system, network, and external APIs using focused protocols and Swift Testing.

scala-testing

8
from marvinrichter/clarc

Scala testing with ScalaTest, MUnit, and ScalaCheck: FunSpec/FlatSpec test structure, property-based testing with forAll, mocking with MockitoSugar, Cats Effect testing with munit-cats-effect (runTest/IOSuite), ZIO Test, Testcontainers-Scala for database integration tests, and CI integration with sbt. Use when writing or reviewing Scala tests.

rust-testing

8
from marvinrichter/clarc

Rust testing patterns — unit tests with mockall, integration tests with sqlx transactions, HTTP handler testing (axum), benchmarks (criterion), property tests (proptest), fuzzing, and CI with cargo-nextest.

rust-testing-advanced

8
from marvinrichter/clarc

Advanced Rust testing anti-patterns and corrections — cfg(test) placement, expect() over unwrap(), mockall expectation ordering, executor mixing (#[tokio::test] vs block_on), PgPool isolation with

r-testing

8
from marvinrichter/clarc

R testing patterns: testthat 3e with expect_* assertions, snapshot testing, mocking with mockery and httptest2, covr code coverage, lintr static analysis, property-based testing with hedgehog, testing Shiny apps with shinytest2. Use when writing or reviewing R tests.

python-testing

8
from marvinrichter/clarc

Python testing strategies using pytest, TDD methodology, fixtures, mocking, and parametrization. Core testing fundamentals.

python-testing-advanced

8
from marvinrichter/clarc

Advanced Python testing — async testing with pytest-asyncio, exception/side-effect testing, test organization, common patterns (API, database, class methods), pytest configuration, and CLI reference. Extends python-testing.

php-testing

8
from marvinrichter/clarc

PHP testing patterns: PHPUnit 11 with mocks and data providers, Pest v3 with expectations and datasets, Laravel feature/HTTP tests with RefreshDatabase, Symfony WebTestCase, PHPStan static analysis, Infection mutation testing. Use when writing or reviewing PHP tests.

load-testing

8
from marvinrichter/clarc

Load and performance testing with k6 (TypeScript/Go/any HTTP) and Locust (Python). Covers test types (smoke, load, stress, spike, soak), SLO thresholds, CI integration, and interpreting results.