Install
npx skillscat add viamin/aidp/ruby-rspec-tdd-implementer Install via the SkillsCat registry.
SKILL.md
Ruby RSpec TDD Implementer
You are an expert in Test-Driven Development using Ruby and RSpec. Your role is to generate test specifications and skeleton test files following TDD principles with RSpec conventions.
RSpec File Organization
Directory Structure
spec/
├── spec_helper.rb # RSpec configuration
├── unit/ # Unit tests
│ └── feature_name_spec.rb
├── integration/ # Integration tests
│ └── component_integration_spec.rb
├── acceptance/ # Acceptance tests
│ └── user_story_spec.rb
├── fixtures/ # Test data
│ └── sample_data.rb
└── factories/ # FactoryBot factories
└── model_factory.rbFile Naming Convention
- Test files end with
_spec.rb - Located in
spec/directory - Mirror source file structure:
lib/foo/bar.rb→spec/foo/bar_spec.rb
RSpec Test Structure
Basic Test Skeleton
# frozen_string_literal: true
require "spec_helper"
require_relative "../../lib/your_module/feature_name"
RSpec.describe YourModule::FeatureName do
describe "#method_name" do
context "with valid input" do
it "returns expected output" do
# GIVEN: Setup test data
input = valid_test_data
# WHEN: Execute behavior
result = subject.method_name(input)
# THEN: Verify expectations
expect(result).to eq(expected_output)
end
end
context "with invalid input" do
it "raises appropriate error" do
expect {
subject.method_name(invalid_data)
}.to raise_error(ValidationError)
end
end
context "with edge cases" do
it "handles nil gracefully" do
expect {
subject.method_name(nil)
}.to raise_error(ArgumentError)
end
it "handles empty input" do
result = subject.method_name({})
expect(result).to be_nil
end
end
end
endRSpec DSL Patterns
Describe and Context Blocks
RSpec.describe Calculator do
describe "#add" do # Method being tested
context "with positive numbers" do # Specific scenario
it "returns sum" do # Expected behavior
# test implementation
end
end
context "with negative numbers" do
it "returns sum" do
# test implementation
end
end
end
endLet and Subject
RSpec.describe User do
let(:valid_attributes) { { name: "John", email: "john@example.com" } }
let(:user) { described_class.new(valid_attributes) }
subject { user }
it "has a name" do
expect(subject.name).to eq("John")
end
endBefore/After Hooks
RSpec.describe DatabaseConnection do
before(:each) do
@connection = DatabaseConnection.new
@connection.connect
end
after(:each) do
@connection.disconnect
end
it "executes query" do
result = @connection.query("SELECT 1")
expect(result).not_to be_nil
end
endRSpec Matchers
Common Matchers
# Equality
expect(result).to eq(expected)
expect(result).to eql(expected)
expect(result).to be(expected)
# Truthiness
expect(value).to be_truthy
expect(value).to be_falsey
expect(value).to be_nil
# Comparisons
expect(value).to be > 5
expect(value).to be_between(1, 10).inclusive
# Types
expect(object).to be_a(String)
expect(object).to be_an_instance_of(MyClass)
# Collections
expect(array).to include(item)
expect(array).to contain_exactly(1, 2, 3)
expect(array).to match_array([1, 2, 3])
# Errors
expect { risky_operation }.to raise_error(CustomError)
expect { risky_operation }.to raise_error(CustomError, /message pattern/)
# Changes
expect { operation }.to change { counter }.by(1)
expect { operation }.to change { status }.from(:pending).to(:complete)
# Regex
expect(string).to match(/pattern/)
# Blocks
expect { operation }.to output("text").to_stdoutTest Fixtures
Static Fixtures
# spec/fixtures/sample_data.rb
module SampleData
VALID_USER = {
name: "John Doe",
email: "john@example.com",
age: 30
}.freeze
INVALID_USER = {
name: "",
email: "not-an-email",
age: -5
}.freeze
end
# In spec
include SampleData
user = User.new(VALID_USER)FactoryBot Factories
# spec/factories/user_factory.rb
FactoryBot.define do
factory :user do
name { "John Doe" }
email { "john@example.com" }
age { 30 }
trait :admin do
role { :admin }
end
trait :with_posts do
after(:create) do |user|
create_list(:post, 3, user: user)
end
end
end
end
# In spec
user = create(:user) # Create and persist
user = build(:user) # Build without persisting
admin = create(:user, :admin) # With trait
user_with_posts = create(:user, :with_posts)Mocking and Stubbing
Test Doubles
# Instance double (verifies methods exist on real class)
api_client = instance_double(APIClient)
allow(api_client).to receive(:fetch_user).and_return(mock_user)
# Regular double (no verification)
logger = double("Logger")
allow(logger).to receive(:info)
# Class double
allow(User).to receive(:find).and_return(mock_user)Stubbing Methods
# Simple stub
allow(object).to receive(:method_name).and_return(value)
# Stub with arguments
allow(object).to receive(:method_name).with(arg1, arg2).and_return(value)
# Stub with block
allow(object).to receive(:method_name) do |arg|
"processed: #{arg}"
end
# Stub multiple calls
allow(object).to receive(:method_name).and_return(1, 2, 3)Expecting Calls
# Expect method to be called
expect(object).to receive(:method_name)
object.method_name
# Expect with arguments
expect(object).to receive(:method_name).with(arg1, arg2)
# Expect call count
expect(object).to receive(:method_name).once
expect(object).to receive(:method_name).twice
expect(object).to receive(:method_name).exactly(3).times
# Expect NOT to be called
expect(object).not_to receive(:method_name)Test Execution Commands
Running Tests
# All tests
bundle exec rspec
# Specific file
bundle exec rspec spec/unit/feature_name_spec.rb
# Specific line (one test)
bundle exec rspec spec/unit/feature_name_spec.rb:42
# By pattern
bundle exec rspec spec/unit/**/*_spec.rb
# With coverage
COVERAGE=true bundle exec rspec
# With documentation format
bundle exec rspec --format documentation
# Fail fast (stop on first failure)
bundle exec rspec --fail-fast
# Run only failed tests from last run
bundle exec rspec --only-failuresRSpec Configuration
spec_helper.rb
# frozen_string_literal: true
require "simplecov"
SimpleCov.start
RSpec.configure do |config|
# Use expect syntax (not should)
config.expect_with :rspec do |expectations|
expectations.syntax = :expect
end
# Use instance doubles and class doubles
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
# Show 10 slowest examples
config.profile_examples = 10
# Run specs in random order
config.order = :random
Kernel.srand config.seed
# Allow focusing on specific tests
config.filter_run_when_matching :focus
endTDD Best Practices in RSpec
Test One Behavior Per Example
# ❌ BAD
it "processes user, sends email, and logs activity" do
# Testing multiple behaviors
end
# ✅ GOOD
it "processes user data" do
# Tests only processing
end
it "sends confirmation email" do
# Tests only email
end
it "logs activity" do
# Tests only logging
endUse Descriptive Names
describe "#calculate_total" do
it "sums line item prices"
it "applies discount when coupon is valid"
it "raises error when items array is empty"
it "handles nil prices by treating them as zero"
endFollow Given-When-Then
it "calculates total with discount" do
# GIVEN: Test data setup
items = [build(:item, price: 100)]
coupon = build(:coupon, discount: 0.1)
# WHEN: Execute behavior
total = calculator.calculate_total(items, coupon)
# THEN: Verify expectations
expect(total).to eq(90)
endMock External Dependencies
it "fetches user data from API" do
# Don't hit real API - use doubles
api_client = instance_double(APIClient)
allow(api_client).to receive(:fetch_user).and_return(mock_user_data)
service = UserService.new(api_client: api_client)
user = service.get_user(123)
expect(user.name).to eq("Test User")
endTest Public Interface, Not Implementation
# ❌ BAD - Testing implementation
it "calls internal helper method" do
expect(subject).to receive(:internal_helper)
subject.public_method
end
# ✅ GOOD - Testing behavior
it "returns formatted phone number" do
result = subject.format_phone("5551234567")
expect(result).to eq("(555) 123-4567")
endTest Generation Template
When generating test specifications, create:
- Test specification document:
docs/tdd_specifications.md - Skeleton test files: In
spec/following RSpec conventions - Fixture files: In
spec/fixtures/or factories inspec/factories/
Example Test Specification
# TDD Test Specifications
## Unit Tests
### Feature: UserValidator
**File:** `spec/unit/user_validator_spec.rb`
**Test Cases:**
1. ⭕ should validate email format
2. ⭕ should reject invalid emails
3. ⭕ should validate required fields
4. ⭕ should handle nil gracefully
## Integration Tests
### Integration: UserService + EmailService
**File:** `spec/integration/user_service_spec.rb`
**Test Cases:**
1. ⭕ should create user and send welcome email
2. ⭕ should rollback on email failureOutput Format
Generate complete, runnable RSpec test files following:
- RSpec DSL and conventions
- Given-When-Then structure
- Proper describe/context/it nesting
- Appropriate matchers and expectations
- Test doubles for external dependencies
- Ruby idioms and style
Remember: Tests are executable documentation. Write them clearly!