Ruby testing patterns with RSpec and Minitest, including mocking, test data, TDD workflow, and async testing strategies
From sp-eccnpx claudepluginhub faisalalqarni/sp-ecc --plugin sp-eccThis skill uses the workspace's default tool permissions.
Comprehensive Ruby testing patterns covering RSpec and Minitest frameworks for writing maintainable, reliable tests following TDD methodology.
RSpec uses a behavior-driven development (BDD) syntax with describe, context, and it blocks:
# spec/calculator_spec.rb
require 'spec_helper'
RSpec.describe Calculator do
# describe - groups related tests for a class/module
describe '#add' do
# context - describes a specific scenario
context 'with positive numbers' do
# it - defines a single test case
it 'returns the sum' do
calculator = Calculator.new
result = calculator.add(2, 3)
expect(result).to eq(5)
end
end
context 'with negative numbers' do
it 'returns the correct sum' do
calculator = Calculator.new
result = calculator.add(-1, -2)
expect(result).to eq(-3)
end
end
context 'with zero' do
it 'returns the other number' do
calculator = Calculator.new
expect(calculator.add(5, 0)).to eq(5)
expect(calculator.add(0, 3)).to eq(3)
end
end
end
describe '#divide' do
it 'returns the quotient' do
calculator = Calculator.new
expect(calculator.divide(10, 2)).to eq(5)
end
context 'when dividing by zero' do
it 'raises a ZeroDivisionError' do
calculator = Calculator.new
expect { calculator.divide(10, 0) }.to raise_error(ZeroDivisionError)
end
end
end
end
Use subject and let for DRY test code:
RSpec.describe User do
# subject - the main object under test
subject(:user) { User.new(name: 'Alice', email: 'alice@example.com') }
# let - lazy-evaluated variable (only created when used)
let(:admin) { User.new(name: 'Bob', role: 'admin') }
# let! - eager evaluation (created before each test)
let!(:saved_user) { User.create(name: 'Charlie', email: 'charlie@example.com') }
describe '#full_profile' do
it 'includes name and email' do
expect(user.full_profile).to include('Alice', 'alice@example.com')
end
end
describe '#admin?' do
context 'when user is admin' do
subject(:user) { admin }
it 'returns true' do
expect(user.admin?).to be true
end
end
context 'when user is not admin' do
it 'returns false' do
expect(user.admin?).to be false
end
end
end
end
RSpec.describe Database do
# Runs before each test in this describe block
before(:each) do
@connection = Database.connect
end
# Runs after each test
after(:each) do
@connection.close
end
# Runs once before all tests in this describe block
before(:all) do
@test_db = setup_test_database
end
# Runs once after all tests
after(:all) do
@test_db.teardown
end
# around - wraps each test
around(:each) do |example|
Database.transaction do
example.run
raise ActiveRecord::Rollback # Rollback after each test
end
end
it 'performs a query' do
result = @connection.query('SELECT 1')
expect(result).not_to be_nil
end
end
RSpec.describe 'RSpec Matchers' do
describe 'Equality matchers' do
it 'uses eq for value equality' do
expect(1 + 1).to eq(2)
end
it 'uses eql for value and type equality' do
expect(2.0).not_to eql(2)
expect(2).to eql(2)
end
it 'uses equal for object identity' do
a = 'hello'
b = a
expect(a).to equal(b)
expect(a).not_to equal('hello') # Different object
end
end
describe 'Comparison matchers' do
it 'checks greater than' do
expect(5).to be > 3
expect(5).to be >= 5
end
it 'checks less than' do
expect(3).to be < 5
expect(3).to be <= 3
end
it 'checks between' do
expect(5).to be_between(1, 10).inclusive
expect(5).to be_between(1, 10).exclusive
end
end
describe 'Type matchers' do
it 'checks class type' do
expect('hello').to be_a(String)
expect(123).to be_an(Integer)
expect([1, 2]).to be_an_instance_of(Array)
end
it 'checks for nil' do
expect(nil).to be_nil
expect('text').not_to be_nil
end
it 'checks truthiness' do
expect(true).to be_truthy
expect(false).to be_falsey
expect(nil).to be_falsey
expect(0).to be_truthy # 0 is truthy in Ruby
end
end
describe 'Collection matchers' do
it 'checks inclusion' do
expect([1, 2, 3]).to include(2)
expect([1, 2, 3]).to include(1, 3)
expect({ a: 1, b: 2 }).to include(a: 1)
end
it 'checks array contents' do
expect([1, 2, 3]).to match_array([3, 2, 1]) # Order doesn't matter
expect([1, 2, 3]).to eq([1, 2, 3]) # Order matters
expect([1, 2, 3]).to contain_exactly(3, 2, 1)
end
it 'checks emptiness' do
expect([]).to be_empty
expect([1]).not_to be_empty
end
end
describe 'String matchers' do
it 'checks string content' do
expect('hello world').to start_with('hello')
expect('hello world').to end_with('world')
expect('hello world').to match(/wo\w+d/)
end
end
describe 'Error matchers' do
it 'checks for raised errors' do
expect { 1 / 0 }.to raise_error(ZeroDivisionError)
expect { raise 'boom' }.to raise_error('boom')
expect { raise ArgumentError, 'bad arg' }.to raise_error(ArgumentError, /bad/)
end
end
describe 'Change matchers' do
it 'checks for state changes' do
array = []
expect { array << 1 }.to change { array.size }.from(0).to(1)
expect { array << 2 }.to change { array.size }.by(1)
end
end
describe 'Predicate matchers' do
it 'converts predicates to matchers' do
expect([]).to be_empty # Calls .empty?
expect(0).to be_zero # Calls .zero?
expect(nil).to be_nil # Calls .nil?
end
it 'uses have_ for collection predicates' do
expect([1, 2, 3]).to have_attributes(size: 3)
end
end
end
# spec/support/shared_examples/searchable.rb
RSpec.shared_examples 'searchable' do
describe '#search' do
it 'finds matching records' do
expect(described_class.search('test')).to include(matching_record)
end
it 'returns empty array when no matches' do
expect(described_class.search('nonexistent')).to be_empty
end
end
end
# spec/models/user_spec.rb
RSpec.describe User do
let(:matching_record) { User.create(name: 'test user') }
it_behaves_like 'searchable'
end
# spec/models/post_spec.rb
RSpec.describe Post do
let(:matching_record) { Post.create(title: 'test post') }
it_behaves_like 'searchable'
end
# Shared context
RSpec.shared_context 'with authenticated user' do
let(:user) { create(:user) }
let(:auth_token) { generate_token(user) }
before do
allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user)
end
end
# Usage
RSpec.describe PostsController do
include_context 'with authenticated user'
it 'allows post creation' do
post :create, params: { post: { title: 'Test' } }
expect(response).to have_http_status(:created)
end
end
# test/calculator_test.rb
require 'minitest/autorun'
class CalculatorTest < Minitest::Test
def setup
@calculator = Calculator.new
end
def teardown
# Cleanup after each test
end
def test_add_positive_numbers
result = @calculator.add(2, 3)
assert_equal 5, result
end
def test_add_negative_numbers
result = @calculator.add(-1, -2)
assert_equal(-3, result)
end
def test_divide_by_zero_raises_error
assert_raises(ZeroDivisionError) do
@calculator.divide(10, 0)
end
end
def test_multiply_returns_product
assert_equal 12, @calculator.multiply(3, 4)
end
end
require 'minitest/autorun'
describe Calculator do
before do
@calculator = Calculator.new
end
describe '#add' do
it 'adds two positive numbers' do
_(@calculator.add(2, 3)).must_equal 5
end
it 'adds negative numbers' do
expect(@calculator.add(-1, -2)).must_equal(-3)
end
end
describe '#divide' do
it 'raises error when dividing by zero' do
_ { @calculator.divide(10, 0) }.must_raise ZeroDivisionError
end
end
end
class AssertionsTest < Minitest::Test
def test_equality
assert_equal 4, 2 + 2
refute_equal 5, 2 + 2
end
def test_nil
assert_nil nil
refute_nil 'value'
end
def test_inclusion
assert_includes [1, 2, 3], 2
refute_includes [1, 2, 3], 4
end
def test_match
assert_match(/hello/, 'hello world')
refute_match(/goodbye/, 'hello world')
end
def test_empty
assert_empty []
refute_empty [1]
end
def test_instance_of
assert_instance_of String, 'hello'
assert_kind_of Numeric, 42
end
def test_responds_to
assert_responds_to 'string', :upcase
end
def test_operator
assert_operator 5, :>, 3
assert_operator 10, :<=, 10
end
def test_raises
assert_raises(ArgumentError) { raise ArgumentError }
end
def test_silent
assert_silent { 1 + 1 }
end
def test_output
assert_output("Hello\n") { puts 'Hello' }
end
end
Use RSpec when:
Use Minitest when:
spec/
├── spec_helper.rb # RSpec configuration
├── rails_helper.rb # Rails-specific configuration (if using Rails)
├── support/ # Shared helpers and configurations
│ ├── factory_bot.rb
│ ├── database_cleaner.rb
│ └── shared_examples/
│ └── searchable.rb
├── models/ # Model tests
│ ├── user_spec.rb
│ └── post_spec.rb
├── controllers/ # Controller tests
│ └── posts_controller_spec.rb
├── services/ # Service object tests
│ └── user_registration_spec.rb
├── lib/ # Library code tests
│ └── calculator_spec.rb
├── requests/ # API/Integration tests
│ └── api/
│ └── users_spec.rb
├── features/ # Feature/Acceptance tests (with Capybara)
│ └── user_login_spec.rb
└── system/ # System tests (Rails 5.1+)
└── user_signup_spec.rb
test/
├── test_helper.rb # Test configuration
├── support/ # Shared helpers
│ └── factory_helper.rb
├── models/ # Model tests
│ ├── user_test.rb
│ └── post_test.rb
├── controllers/ # Controller tests
│ └── posts_controller_test.rb
├── integration/ # Integration tests
│ └── user_login_test.rb
├── lib/ # Library code tests
│ └── calculator_test.rb
└── fixtures/ # Test data (if using fixtures)
├── users.yml
└── posts.yml
# RSpec - use descriptive strings
RSpec.describe User do
describe '.find_by_email' do
context 'when email exists' do
it 'returns the user' do
end
end
context 'when email does not exist' do
it 'returns nil' do
end
end
end
end
# Minitest - use test_ prefix
class UserTest < Minitest::Test
def test_find_by_email_when_email_exists
# ...
end
def test_find_by_email_when_email_does_not_exist
# ...
end
end
RSpec.describe UserService do
describe '#send_welcome_email' do
it 'sends email via mailer' do
user = User.new(email: 'test@example.com')
mailer = instance_double(UserMailer)
# Stub method call
allow(UserMailer).to receive(:new).and_return(mailer)
allow(mailer).to receive(:send_welcome)
service = UserService.new
service.send_welcome_email(user)
# Verify method was called
expect(mailer).to have_received(:send_welcome).with(user)
end
end
describe '#process_payment' do
it 'charges the credit card' do
payment_gateway = instance_double(PaymentGateway)
# Stub with return value
allow(payment_gateway).to receive(:charge).and_return(
{ success: true, transaction_id: '123' }
)
service = UserService.new(payment_gateway: payment_gateway)
result = service.process_payment(100)
expect(result[:success]).to be true
expect(payment_gateway).to have_received(:charge).with(100)
end
it 'handles payment failure' do
payment_gateway = instance_double(PaymentGateway)
# Stub to raise error
allow(payment_gateway).to receive(:charge).and_raise(PaymentError, 'Card declined')
service = UserService.new(payment_gateway: payment_gateway)
expect {
service.process_payment(100)
}.to raise_error(PaymentError, 'Card declined')
end
end
end
# Double - generic test double
double = double('user', name: 'Alice', email: 'alice@example.com')
expect(double.name).to eq('Alice')
# Instance double - verifies methods exist on class
user = instance_double(User, name: 'Bob', email: 'bob@example.com')
# This would raise error if User doesn't have save method:
allow(user).to receive(:save).and_return(true)
# Class double - for class methods
user_class = class_double(User)
allow(user_class).to receive(:find).with(1).and_return(user)
# Object double - wraps real object
real_user = User.new(name: 'Charlie')
user_double = object_double(real_user, name: 'Charlie')
# Spy - records all method calls
user_spy = spy('user')
service = UserService.new(user: user_spy)
service.update_profile(name: 'New Name')
expect(user_spy).to have_received(:update).with(name: 'New Name')
# Partial stubbing - stub some methods on real object
user = User.new(name: 'Alice')
allow(user).to receive(:save).and_return(true)
# Other methods work normally
expect(user.name).to eq('Alice')
require 'minitest/mock'
class UserServiceTest < Minitest::Test
def test_sends_welcome_email
user = User.new(email: 'test@example.com')
mailer = Minitest::Mock.new
# Expect method call
mailer.expect(:send_welcome, nil, [user])
service = UserService.new(mailer: mailer)
service.send_welcome_email(user)
# Verify expectations were met
mailer.verify
end
def test_processes_payment
payment_gateway = Minitest::Mock.new
payment_gateway.expect(:charge, { success: true, id: '123' }, [100])
service = UserService.new(payment_gateway: payment_gateway)
result = service.process_payment(100)
assert result[:success]
payment_gateway.verify
end
def test_with_stub
user = User.new(name: 'Alice')
# Stub a method temporarily
user.stub :admin?, true do
assert user.admin?
end
# Original behavior restored
refute user.admin?
end
end
# spec/factories/users.rb
FactoryBot.define do
factory :user do
first_name { 'John' }
last_name { 'Doe' }
sequence(:email) { |n| "user#{n}@example.com" }
password { 'password123' }
# Trait for variations
trait :admin do
role { 'admin' }
end
trait :with_posts do
transient do
posts_count { 3 }
end
after(:create) do |user, evaluator|
create_list(:post, evaluator.posts_count, user: user)
end
end
end
factory :post do
title { 'Sample Post' }
body { 'This is a sample post body' }
association :user
published_at { Time.now }
trait :draft do
published_at { nil }
end
trait :with_comments do
after(:create) do |post|
create_list(:comment, 2, post: post)
end
end
end
factory :comment do
body { 'Great post!' }
association :user
association :post
end
end
RSpec.describe User do
# build - builds object without saving
let(:user) { build(:user) }
# create - builds and saves object
let(:saved_user) { create(:user) }
# build_stubbed - builds object with stubbed persistence
let(:stubbed_user) { build_stubbed(:user) }
# create with overrides
let(:admin) { create(:user, :admin, email: 'admin@example.com') }
# create_list - create multiple objects
let(:users) { create_list(:user, 5) }
# create with traits
let(:user_with_posts) { create(:user, :with_posts, posts_count: 10) }
describe '#admin?' do
it 'returns false for regular user' do
expect(user.admin?).to be false
end
it 'returns true for admin user' do
expect(admin.admin?).to be true
end
end
describe '#posts' do
it 'has associated posts' do
expect(user_with_posts.posts.count).to eq(10)
end
end
end
FactoryBot Pros:
FactoryBot Cons:
Fixtures Pros:
Fixtures Cons:
# Fixtures example - test/fixtures/users.yml
alice:
name: Alice
email: alice@example.com
role: admin
bob:
name: Bob
email: bob@example.com
role: user
# Usage in Minitest
class UserTest < ActiveSupport::TestCase
test "alice is admin" do
alice = users(:alice)
assert alice.admin?
end
end
# Generally prefer FactoryBot for most tests
# Use fixtures only for:
# - Reference data (countries, categories)
# - Performance-critical test suites
# - Simple, stable data that doesn't change
# spec/spec_helper.rb or test/test_helper.rb
require 'simplecov'
SimpleCov.start 'rails' do
# Exclude directories from coverage
add_filter '/spec/'
add_filter '/test/'
add_filter '/config/'
add_filter '/vendor/'
# Group coverage by type
add_group 'Models', 'app/models'
add_group 'Controllers', 'app/controllers'
add_group 'Services', 'app/services'
add_group 'Libraries', 'lib'
# Set minimum coverage thresholds
minimum_coverage 80
minimum_coverage_by_file 70
# Refuse coverage below threshold
refuse_coverage_drop :file, 5
end
| Code Type | Target Coverage |
|---|---|
| Critical business logic | 100% |
| Models and services | 90%+ |
| Controllers | 85%+ |
| Libraries | 90%+ |
| Background jobs | 85%+ |
| Views/helpers | 70%+ |
# RSpec with SimpleCov
rspec
# View HTML report
open coverage/index.html
# Minitest with SimpleCov
rake test
# CI environments
COVERAGE=true bundle exec rspec
# Step 1: RED - Write failing test first
RSpec.describe UserRegistration do
describe '#register' do
it 'creates a new user account' do
registration = UserRegistration.new(
email: 'test@example.com',
password: 'password123'
)
result = registration.register
expect(result).to be_success
expect(User.count).to eq(1)
expect(User.last.email).to eq('test@example.com')
end
end
end
# Run: rspec spec/services/user_registration_spec.rb
# Result: FAIL - UserRegistration class doesn't exist
# Step 2: GREEN - Write minimal code to pass
class UserRegistration
def initialize(email:, password:)
@email = email
@password = password
end
def register
User.create(email: @email, password: @password)
Result.success
end
end
# Run: rspec spec/services/user_registration_spec.rb
# Result: PASS
# Step 3: REFACTOR - Improve code while keeping tests green
class UserRegistration
def initialize(email:, password:)
@email = email
@password = password
end
def register
return Result.failure(:invalid_email) unless valid_email?
return Result.failure(:weak_password) unless strong_password?
user = User.create(email: @email, password: @password)
send_welcome_email(user)
Result.success(user: user)
end
private
def valid_email?
@email =~ URI::MailTo::EMAIL_REGEXP
end
def strong_password?
@password.length >= 8
end
def send_welcome_email(user)
UserMailer.welcome(user).deliver_later
end
end
# Run tests again: PASS
# Add more tests for edge cases
# 1. Start with the simplest test
RSpec.describe Calculator do
it 'adds two numbers' do
expect(Calculator.add(2, 3)).to eq(5)
end
end
# 2. Write just enough code to pass
class Calculator
def self.add(a, b)
5 # Simplest implementation
end
end
# 3. Add more specific tests
it 'adds different numbers' do
expect(Calculator.add(1, 1)).to eq(2)
end
# 4. Now implement properly
def self.add(a, b)
a + b
end
# 5. Test edge cases
context 'with edge cases' do
it 'handles zero' do
expect(Calculator.add(0, 0)).to eq(0)
end
it 'handles negative numbers' do
expect(Calculator.add(-5, -3)).to eq(-8)
end
it 'handles large numbers' do
expect(Calculator.add(1_000_000, 2_000_000)).to eq(3_000_000)
end
end
# 6. Refactor with confidence
# RSpec with ActiveJob
RSpec.describe SendWelcomeEmailJob do
include ActiveJob::TestHelper
describe '#perform' do
it 'sends welcome email' do
user = create(:user)
expect {
SendWelcomeEmailJob.perform_later(user.id)
}.to have_enqueued_job(SendWelcomeEmailJob).with(user.id)
end
it 'processes job successfully' do
user = create(:user)
mailer = instance_double(UserMailer)
allow(UserMailer).to receive(:welcome).and_return(mailer)
allow(mailer).to receive(:deliver_now)
perform_enqueued_jobs do
SendWelcomeEmailJob.perform_later(user.id)
end
expect(UserMailer).to have_received(:welcome).with(user)
end
it 'retries on failure' do
user = create(:user)
allow(UserMailer).to receive(:welcome).and_raise(Net::SMTPServerBusy)
expect {
perform_enqueued_jobs do
SendWelcomeEmailJob.perform_later(user.id)
end
}.to raise_error(Net::SMTPServerBusy)
expect(SendWelcomeEmailJob).to have_been_enqueued.at_least(:twice)
end
end
end
RSpec.describe Subscription do
describe '#expired?' do
it 'returns false when not expired' do
subscription = create(:subscription, expires_at: 1.week.from_now)
expect(subscription.expired?).to be false
end
it 'returns true when expired' do
subscription = create(:subscription, expires_at: 1.week.ago)
expect(subscription.expired?).to be true
end
# Using Rails time helpers
it 'expires after expiration date' do
subscription = create(:subscription, expires_at: 3.days.from_now)
travel_to(2.days.from_now) do
expect(subscription.expired?).to be false
end
travel_to(4.days.from_now) do
expect(subscription.expired?).to be true
end
end
end
describe '.expire_today' do
# Freeze time for consistent results
it 'finds subscriptions expiring today' do
freeze_time do
expiring_today = create(:subscription, expires_at: Date.today.end_of_day)
expiring_tomorrow = create(:subscription, expires_at: 1.day.from_now)
expired_yesterday = create(:subscription, expires_at: 1.day.ago)
result = Subscription.expire_today
expect(result).to include(expiring_today)
expect(result).not_to include(expiring_tomorrow, expired_yesterday)
end
end
end
end
RSpec.describe User do
describe 'callbacks' do
it 'sends welcome email after creation' do
mailer = instance_double(UserMailer)
allow(UserMailer).to receive(:welcome).and_return(mailer)
allow(mailer).to receive(:deliver_later)
user = create(:user)
expect(UserMailer).to have_received(:welcome).with(user)
end
it 'generates token before validation' do
user = build(:user, auth_token: nil)
user.valid?
expect(user.auth_token).not_to be_nil
end
it 'normalizes email before save' do
user = create(:user, email: 'USER@EXAMPLE.COM')
expect(user.reload.email).to eq('user@example.com')
end
end
end
RSpec.describe Counter do
describe 'thread safety' do
it 'handles concurrent increments' do
counter = Counter.new(0)
threads = []
10.times do
threads << Thread.new do
100.times { counter.increment }
end
end
threads.each(&:join)
expect(counter.value).to eq(1000)
end
end
end
# Testing with timeout
RSpec.describe LongRunningOperation do
it 'completes within timeout', timeout: 5 do
result = LongRunningOperation.execute
expect(result).to be_success
end
end
Test individual classes/methods in isolation:
# Unit test - tests Calculator in isolation
RSpec.describe Calculator do
describe '#add' do
it 'returns sum of two numbers' do
calculator = Calculator.new
expect(calculator.add(2, 3)).to eq(5)
end
end
end
# Unit test with mocked dependencies
RSpec.describe OrderProcessor do
describe '#process' do
it 'charges customer and sends confirmation' do
order = build(:order, total: 100)
payment_gateway = instance_double(PaymentGateway)
mailer = instance_double(OrderMailer)
allow(payment_gateway).to receive(:charge).and_return(true)
allow(mailer).to receive(:send_confirmation)
processor = OrderProcessor.new(
payment_gateway: payment_gateway,
mailer: mailer
)
processor.process(order)
expect(payment_gateway).to have_received(:charge).with(order.total)
expect(mailer).to have_received(:send_confirmation).with(order)
end
end
end
Test multiple components working together:
# Integration test - tests full workflow
RSpec.describe 'Order processing workflow' do
it 'processes order from creation to fulfillment' do
user = create(:user, email: 'customer@example.com')
product = create(:product, price: 50)
# Create order
order = Order.create(user: user, items: [product])
expect(order).to be_pending
# Process payment
payment_result = PaymentService.new.process(order)
expect(payment_result).to be_success
expect(order.reload).to be_paid
# Fulfill order
FulfillmentService.new.ship(order)
expect(order.reload).to be_shipped
# Check email sent
expect(ActionMailer::Base.deliveries.last.to).to include('customer@example.com')
end
end
# Rails request spec (integration test)
RSpec.describe 'Users API' do
describe 'POST /api/users' do
it 'creates new user and returns user data' do
post '/api/users', params: {
user: {
email: 'test@example.com',
password: 'password123'
}
}, headers: { 'Content-Type': 'application/json' }
expect(response).to have_http_status(:created)
json = JSON.parse(response.body)
expect(json['email']).to eq('test@example.com')
expect(User.last.email).to eq('test@example.com')
end
end
end
Unit Tests (70-80% of tests):
Integration Tests (15-25% of tests):
System/E2E Tests (5-10% of tests):
# Test pyramid in practice
# spec/
# models/ (Unit tests - 40%)
# services/ (Unit tests - 30%)
# lib/ (Unit tests - 10%)
# requests/ (Integration tests - 15%)
# system/ (E2E tests - 5%)
# Fast feedback loop:
# 1. Run unit tests: rspec spec/models spec/services --tag ~slow
# 2. Run integration tests: rspec spec/requests
# 3. Run full suite: rspec
DO:
DON'T:
# RSpec commands
rspec # Run all specs
rspec spec/models # Run specific directory
rspec spec/models/user_spec.rb # Run specific file
rspec spec/models/user_spec.rb:23 # Run specific line
rspec --tag focus # Run focused specs
rspec --tag ~slow # Exclude slow specs
rspec --format documentation # Verbose output
rspec --profile # Show slowest examples
rspec --fail-fast # Stop on first failure
rspec --only-failures # Run only failed specs
# Minitest commands
rake test # Run all tests
ruby test/models/user_test.rb # Run specific file
rake test TEST=test/models/user_test.rb # Run with rake
rake test:models # Run model tests
rake test TESTOPTS="-v" # Verbose output
rake test TESTOPTS="--name=/user/" # Run tests matching pattern
# With coverage
COVERAGE=true rspec
COVERAGE=true rake test
# With Spring for faster startup
spring rspec
spring rake test
# Parallel execution
parallel_rspec spec/
parallel_test test/
Ruby testing combines powerful frameworks (RSpec, Minitest) with excellent tooling (FactoryBot, SimpleCov) to enable comprehensive test coverage. Key principles:
Remember: Good tests are your safety net for refactoring and your documentation for how code should behave.
Designs and optimizes AI agent action spaces, tool definitions, observation formats, error recovery, and context for higher task completion rates.