Overview
Unit testing in Ruby centers around writing isolated tests for individual methods, classes, and modules to verify expected behavior. Ruby provides multiple testing frameworks including the built-in Test::Unit, the popular RSpec with its behavior-driven development approach, and Minitest which combines unit and spec-style testing.
The core testing workflow involves defining test cases that exercise specific functionality, making assertions about expected outcomes, and organizing tests into logical groupings. Ruby's dynamic nature enables powerful testing techniques including runtime method redefinition, flexible mocking systems, and expressive assertion libraries.
# Test::Unit basic structure
require 'test/unit'
class CalculatorTest < Test::Unit::TestCase
def test_addition
calc = Calculator.new
result = calc.add(2, 3)
assert_equal 5, result
end
end
# RSpec structure
RSpec.describe Calculator do
describe '#add' do
it 'returns the sum of two numbers' do
calc = Calculator.new
expect(calc.add(2, 3)).to eq(5)
end
end
end
Test::Unit ships with Ruby and follows traditional xUnit patterns with setup, teardown, and assert methods. RSpec provides domain-specific language for behavior-driven development with describe blocks and expectation syntax. Minitest offers both unit test and spec styles while maintaining compatibility with Test::Unit.
Basic Usage
Test::Unit requires inheriting from Test::Unit::TestCase
and defining methods starting with test_
. The framework automatically discovers and executes these test methods, providing assertion methods for verifying expected behavior.
require 'test/unit'
class StringProcessorTest < Test::Unit::TestCase
def setup
@processor = StringProcessor.new
end
def test_uppercase_conversion
result = @processor.upcase('hello world')
assert_equal 'HELLO WORLD', result
assert_instance_of String, result
end
def test_word_count
result = @processor.word_count('one two three')
assert_equal 3, result
assert result > 0
end
def teardown
@processor = nil
end
end
RSpec organizes tests using describe
and context
blocks to group related functionality, with it
blocks containing individual test cases. The expectation syntax reads naturally and provides detailed failure messages.
RSpec.describe StringProcessor do
let(:processor) { StringProcessor.new }
describe '#upcase' do
it 'converts lowercase text to uppercase' do
expect(processor.upcase('hello')).to eq('HELLO')
end
it 'handles empty strings' do
expect(processor.upcase('')).to eq('')
end
context 'with mixed case input' do
it 'converts entire string to uppercase' do
expect(processor.upcase('HeLLo WoRLd')).to eq('HELLO WORLD')
end
end
end
end
Minitest provides two interfaces: traditional unit tests similar to Test::Unit and spec-style tests resembling RSpec. Both run under the same test runner and share assertion methods.
# Minitest unit style
require 'minitest/autorun'
class ProcessorTest < Minitest::Test
def test_processing
processor = DataProcessor.new
assert_equal 42, processor.calculate(21, 2)
end
end
# Minitest spec style
require 'minitest/autorun'
describe DataProcessor do
it 'calculates correctly' do
processor = DataProcessor.new
_(processor.calculate(21, 2)).must_equal 42
end
end
Test organization follows Ruby's module system. Tests typically mirror the source code structure with corresponding test files in a test/
or spec/
directory. The setup
and teardown
methods handle test initialization and cleanup, running before and after each test method respectively.
Advanced Usage
Ruby's metaprogramming capabilities enable sophisticated testing techniques including dynamic test generation, custom assertion methods, and runtime behavior modification. These patterns help reduce test duplication and create more maintainable test suites.
Dynamic test generation creates multiple similar tests programmatically, reducing repetitive code while maintaining test isolation:
class ValidationTest < Test::Unit::TestCase
INVALID_EMAILS = [
'invalid',
'missing@domain',
'@missinguser.com',
'spaces in@email.com',
'special!chars@domain.com'
]
INVALID_EMAILS.each_with_index do |email, index|
define_method("test_invalid_email_#{index}") do
validator = EmailValidator.new
assert_false validator.valid?(email), "#{email} should be invalid"
end
end
end
Custom assertion methods encapsulate complex verification logic and provide descriptive failure messages:
module CustomAssertions
def assert_valid_json(string, message = nil)
begin
JSON.parse(string)
rescue JSON::ParserError => e
assert(false, message || "Expected valid JSON, got parse error: #{e.message}")
end
end
def assert_responds_to_all(object, methods)
missing = methods.reject { |method| object.respond_to?(method) }
assert missing.empty?, "Object missing methods: #{missing.join(', ')}"
end
end
class APITest < Test::Unit::TestCase
include CustomAssertions
def test_response_format
response = api_client.get('/users')
assert_valid_json(response.body)
assert_responds_to_all(response, [:status, :headers, :body])
end
end
RSpec's shared examples and contexts reduce duplication across similar test scenarios:
RSpec.shared_examples 'a validated model' do |model_class|
let(:model) { model_class.new }
it 'requires name to be present' do
model.name = nil
expect(model.valid?).to be false
expect(model.errors[:name]).to include("can't be blank")
end
it 'requires name to be unique' do
existing = model_class.create!(name: 'Test Name')
model.name = 'Test Name'
expect(model.valid?).to be false
expect(model.errors[:name]).to include('has already been taken')
end
end
RSpec.describe User do
include_examples 'a validated model', User
end
RSpec.describe Product do
include_examples 'a validated model', Product
end
Test doubles and stubbing control external dependencies during testing. Ruby provides multiple approaches for creating mock objects and stubbing method calls:
class WeatherServiceTest < Test::Unit::TestCase
def test_temperature_conversion
api_client = mock('api_client')
api_client.expects(:get).with('/weather/current').returns(
{ 'temperature' => '72F', 'humidity' => '45%' }
)
service = WeatherService.new(api_client)
temp = service.current_temperature_celsius
assert_equal 22.2, temp.round(1)
end
end
Testing Strategies
Effective unit testing requires strategic approaches to test organization, data management, and dependency isolation. Ruby testing frameworks provide multiple patterns for structuring tests that remain maintainable as codebases grow.
Test fixtures provide consistent data across test runs while avoiding brittle dependencies on external state. Ruby supports multiple fixture strategies from simple instance variables to complex factory systems:
class UserTest < Test::Unit::TestCase
def setup
@valid_user_attrs = {
name: 'John Doe',
email: 'john@example.com',
age: 30
}
@user = User.new(@valid_user_attrs)
end
def test_full_name_generation
assert_equal 'John Doe', @user.full_name
end
def test_adult_status
assert @user.adult?
child_user = User.new(@valid_user_attrs.merge(age: 15))
assert_false child_user.adult?
end
end
RSpec's let
and let!
declarations create lazy-loaded test data that gets memoized within individual test runs but resets between tests:
RSpec.describe Order do
let(:customer) { Customer.new(name: 'Jane Smith') }
let!(:product) { Product.create!(name: 'Widget', price: 10.00) }
let(:order) { Order.new(customer: customer) }
describe '#total' do
before do
order.add_item(product, quantity: 3)
order.add_item(Product.create!(name: 'Gadget', price: 15.00), quantity: 2)
end
it 'calculates total from all items' do
expect(order.total).to eq(60.00)
end
end
end
Test parameterization handles multiple input scenarios without duplicating test logic. Both frameworks support parameterized testing patterns:
class InputValidationTest < Test::Unit::TestCase
VALIDATION_CASES = [
{ input: 'valid@email.com', expected: true, description: 'standard email' },
{ input: 'user+tag@domain.co.uk', expected: true, description: 'plus addressing' },
{ input: 'invalid.email', expected: false, description: 'missing @ symbol' },
{ input: '@domain.com', expected: false, description: 'missing username' },
{ input: 'user@', expected: false, description: 'missing domain' }
]
VALIDATION_CASES.each do |test_case|
define_method("test_email_validation_#{test_case[:description].gsub(/\s+/, '_')}") do
validator = EmailValidator.new
result = validator.valid?(test_case[:input])
assert_equal test_case[:expected], result,
"Expected #{test_case[:input]} to be #{test_case[:expected] ? 'valid' : 'invalid'}"
end
end
end
Dependency injection and mocking isolate units under test from external dependencies. Ruby's duck typing enables flexible test double creation:
class PaymentProcessorTest < Test::Unit::TestCase
def test_successful_payment_processing
# Create test doubles
gateway = mock('payment_gateway')
logger = mock('logger')
# Set expectations
gateway.expects(:charge).with(
amount: 100.00,
card_token: 'tok_123'
).returns(
success: true,
transaction_id: 'txn_456'
)
logger.expects(:info).with('Payment processed: txn_456')
# Execute test
processor = PaymentProcessor.new(gateway: gateway, logger: logger)
result = processor.process_payment(amount: 100.00, card_token: 'tok_123')
assert result.success?
assert_equal 'txn_456', result.transaction_id
end
end
Error Handling & Debugging
Unit tests must handle both expected exceptions from the code under test and unexpected failures in the test infrastructure itself. Ruby testing frameworks provide assertion methods for verifying exception behavior and debugging test failures.
Exception testing verifies that methods raise appropriate errors under specific conditions. Test::Unit provides assert_raise
and assert_nothing_raised
for exception verification:
class FileProcessorTest < Test::Unit::TestCase
def test_file_not_found_handling
processor = FileProcessor.new
assert_raise(FileNotFoundError) do
processor.read_file('/nonexistent/path.txt')
end
end
def test_invalid_format_handling
processor = FileProcessor.new
exception = assert_raise(InvalidFormatError) do
processor.parse_csv('invalid,csv,content\nwith,missing')
end
assert_match /column count mismatch/, exception.message
assert_equal 2, exception.line_number
end
def test_successful_processing
processor = FileProcessor.new
assert_nothing_raised do
result = processor.read_file('test/fixtures/valid_data.csv')
assert_instance_of Array, result
end
end
end
RSpec provides expect { }.to raise_error
with flexible matching capabilities for exception class, message patterns, and custom matchers:
RSpec.describe FileProcessor do
describe '#parse_csv' do
it 'raises InvalidFormatError for malformed CSV' do
processor = FileProcessor.new
expect {
processor.parse_csv("incomplete,csv\nrow")
}.to raise_error(InvalidFormatError, /column count mismatch/)
end
it 'raises specific error with line information' do
processor = FileProcessor.new
expect {
processor.parse_csv("header1,header2\nvalue1")
}.to raise_error(an_instance_of(InvalidFormatError)
.and(having_attributes(line_number: 2)))
end
end
end
Test failure debugging requires systematic approaches to isolate the source of failures. Adding debugging output helps track test execution flow:
class ComplexCalculationTest < Test::Unit::TestCase
def test_statistical_analysis
data = [1, 2, 3, 4, 5, 100] # Outlier intentionally included
analyzer = StatisticalAnalyzer.new(data)
# Debug intermediate calculations
puts "Mean: #{analyzer.mean}"
puts "Median: #{analyzer.median}"
puts "Standard deviation: #{analyzer.standard_deviation}"
# Multiple related assertions
assert_in_delta 19.17, analyzer.mean, 0.01
assert_equal 3.5, analyzer.median
assert_in_delta 37.99, analyzer.standard_deviation, 0.01
end
end
Advanced Usage
Advanced unit testing leverages Ruby's metaprogramming features, sophisticated mocking capabilities, and framework-specific extensions to create maintainable and expressive test suites.
Method aliasing and monkey patching enable temporary behavior modification during testing without affecting production code:
class TimeDependentTest < Test::Unit::TestCase
def setup
@original_now = Time.method(:now)
@fixed_time = Time.new(2024, 1, 15, 12, 0, 0)
end
def test_time_sensitive_calculation
# Stub Time.now for consistent results
Time.define_singleton_method(:now) { @fixed_time }
scheduler = TaskScheduler.new
next_run = scheduler.next_execution_time
assert_equal Time.new(2024, 1, 15, 13, 0, 0), next_run
ensure
# Restore original behavior
Time.define_singleton_method(:now, &@original_now)
end
end
RSpec's sophisticated mocking system supports method stubbing, spy verification, and complex expectation chains:
RSpec.describe NotificationService do
describe '#send_welcome_email' do
let(:email_client) { instance_double('EmailClient') }
let(:template_engine) { instance_double('TemplateEngine') }
let(:service) { NotificationService.new(email_client, template_engine) }
let(:user) { User.new(name: 'Alice', email: 'alice@example.com') }
before do
allow(template_engine).to receive(:render)
.with('welcome_template', user: user)
.and_return('<h1>Welcome Alice!</h1>')
allow(email_client).to receive(:send_email)
.and_return(delivery_id: 'msg_123')
end
it 'renders template and sends email' do
result = service.send_welcome_email(user)
expect(template_engine).to have_received(:render)
.with('welcome_template', user: user)
expect(email_client).to have_received(:send_email)
.with(
to: 'alice@example.com',
subject: 'Welcome!',
body: '<h1>Welcome Alice!</h1>'
)
expect(result[:delivery_id]).to eq('msg_123')
end
end
end
Custom matchers extend testing frameworks with domain-specific assertions that improve test readability and provide better failure messages:
RSpec::Matchers.define :be_valid_credit_card do
match do |number|
@number = number.to_s.gsub(/\s+/, '')
@number =~ /^\d{13,19}$/ && luhn_valid?(@number)
end
failure_message do
if @number !~ /^\d{13,19}$/
"expected #{@number} to contain 13-19 digits, got #{@number.length}"
else
"expected #{@number} to pass Luhn algorithm validation"
end
end
def luhn_valid?(number)
digits = number.chars.map(&:to_i).reverse
sum = digits.each_with_index.sum do |digit, index|
if index.odd?
doubled = digit * 2
doubled > 9 ? doubled - 9 : doubled
else
digit
end
end
sum % 10 == 0
end
end
# Usage
RSpec.describe CreditCard do
it 'validates card numbers' do
expect('4532123456789012').to be_valid_credit_card
expect('1234567890123456').not_to be_valid_credit_card
end
end
Shared contexts and modules provide reusable test infrastructure across multiple test files:
module DatabaseHelpers
def with_clean_database
DatabaseCleaner.start
yield
ensure
DatabaseCleaner.clean
end
def create_test_user(attrs = {})
User.create!({
name: 'Test User',
email: 'test@example.com'
}.merge(attrs))
end
end
class IntegrationTest < Test::Unit::TestCase
include DatabaseHelpers
def test_user_creation_workflow
with_clean_database do
user = create_test_user(name: 'Alice')
assert_equal 'Alice', user.name
assert user.persisted?
end
end
end
Error Handling & Debugging
Robust error handling in tests ensures clear failure messages and helps identify the root causes of test failures. Ruby testing frameworks provide multiple approaches for handling both expected and unexpected errors during test execution.
Assertion messages provide context when tests fail, making debugging faster and more accurate:
class ArrayProcessorTest < Test::Unit::TestCase
def test_array_sorting_with_custom_comparator
processor = ArrayProcessor.new
input = [3, 1, 4, 1, 5, 9, 2, 6]
result = processor.sort_with_comparator(input) { |a, b| b <=> a }
expected = [9, 6, 5, 4, 3, 2, 1, 1]
assert_equal expected, result,
"Expected descending sort, got #{result}. Original input: #{input}"
# Verify original array unchanged
assert_equal [3, 1, 4, 1, 5, 9, 2, 6], input,
"Original array should not be modified"
end
end
Exception rescue blocks in tests handle setup failures and provide fallback behavior when external dependencies are unavailable:
class ExternalServiceTest < Test::Unit::TestCase
def setup
begin
@service = ExternalAPIClient.new
@service.ping # Verify connectivity
@service_available = true
rescue ConnectionError, TimeoutError => e
@service_available = false
@skip_message = "External service unavailable: #{e.message}"
end
end
def test_api_data_retrieval
skip(@skip_message) unless @service_available
data = @service.fetch_user_data(user_id: 123)
assert_instance_of Hash, data
assert data.key?('name')
assert data.key?('email')
end
end
RSpec's aggregate failures collect multiple assertion failures within a single test, providing comprehensive failure information:
RSpec.describe UserProfile do
describe '#validate' do
it 'validates all required fields' do
profile = UserProfile.new
result = profile.validate
aggregate_failures 'validation results' do
expect(result.valid?).to be false
expect(result.errors).to include('Name is required')
expect(result.errors).to include('Email is required')
expect(result.errors).to include('Age must be positive')
expect(result.error_count).to eq(3)
end
end
end
end
Debugging complex test failures often requires inspecting object state and method call sequences. Ruby's introspection capabilities combined with testing framework hooks provide powerful debugging tools:
class StateTrackingTest < Test::Unit::TestCase
def setup
@call_log = []
@original_methods = {}
end
def track_method_calls(object, method_name)
@original_methods[object] ||= {}
@original_methods[object][method_name] = object.method(method_name)
object.define_singleton_method(method_name) do |*args, **kwargs|
@call_log << {
object: object.class.name,
method: method_name,
args: args,
kwargs: kwargs,
timestamp: Time.now
}
@original_methods[object][method_name].call(*args, **kwargs)
end
end
def test_complex_interaction_sequence
processor = DataProcessor.new
validator = DataValidator.new
track_method_calls(processor, :normalize)
track_method_calls(validator, :validate)
pipeline = ProcessingPipeline.new(processor, validator)
pipeline.process(['raw', 'data', 'items'])
# Verify call sequence
assert_equal 3, @call_log.count { |log| log[:method] == :normalize }
assert_equal 1, @call_log.count { |log| log[:method] == :validate }
# Print debug information on failure
if @call_log.empty?
flunk "No method calls were tracked. Pipeline: #{pipeline.inspect}"
end
end
def teardown
@original_methods.each do |object, methods|
methods.each do |method_name, original_method|
object.define_singleton_method(method_name, &original_method)
end
end
end
end
Common Pitfalls
Unit testing in Ruby presents several common pitfalls that can lead to brittle, slow, or unreliable tests. Understanding these issues helps create more maintainable test suites.
Test interdependence occurs when tests rely on execution order or shared state, causing failures when tests run in different sequences:
# PROBLEMATIC: Tests depend on execution order
class ProblematicTest < Test::Unit::TestCase
@@user_count = 0
def test_user_creation
@@user_count += 1
user = User.create!(name: "User #{@@user_count}")
assert_equal "User 1", user.name # Fails if run after other tests
end
def test_user_deletion
User.create!(name: "User #{@@user_count + 1}")
@@user_count += 1
User.delete_all
assert_equal 0, User.count # Depends on previous test state
end
end
# BETTER: Isolated tests with proper setup
class ImprovedTest < Test::Unit::TestCase
def setup
User.delete_all # Clean slate for each test
end
def test_user_creation
user = User.create!(name: "Test User")
assert_equal "Test User", user.name
assert_equal 1, User.count
end
def test_user_deletion
user = User.create!(name: "Test User")
user.delete
assert_equal 0, User.count
end
end
Over-mocking reduces test value by mocking the system under test instead of its dependencies. Tests should verify real behavior rather than mock interactions:
# PROBLEMATIC: Over-mocked test
class OverMockedTest < Test::Unit::TestCase
def test_calculation_engine
engine = mock('calculation_engine')
engine.expects(:add).with(2, 3).returns(5)
engine.expects(:multiply).with(5, 2).returns(10)
# This test verifies mock setup, not actual calculation logic
result = engine.add(2, 3)
final = engine.multiply(result, 2)
assert_equal 10, final
end
end
# BETTER: Test real object, mock dependencies only
class ProperTest < Test::Unit::TestCase
def test_calculation_engine
logger = mock('logger')
logger.expects(:debug).with('Performing calculation')
engine = CalculationEngine.new(logger: logger)
result = engine.calculate(2, 3, :add_then_double)
assert_equal 10, result # Verifies actual calculation logic
end
end
Testing implementation details instead of behavior creates brittle tests that break during refactoring. Focus on observable outcomes rather than internal method calls:
# PROBLEMATIC: Testing implementation details
RSpec.describe ShoppingCart do
it 'calls internal calculation methods' do
cart = ShoppingCart.new
allow(cart).to receive(:calculate_subtotal)
allow(cart).to receive(:apply_discounts)
allow(cart).to receive(:calculate_tax)
cart.total_price
expect(cart).to have_received(:calculate_subtotal)
expect(cart).to have_received(:apply_discounts)
expect(cart).to have_received(:calculate_tax)
end
end
# BETTER: Testing behavior and outcomes
RSpec.describe ShoppingCart do
describe '#total_price' do
let(:cart) { ShoppingCart.new }
before do
cart.add_item(Product.new(price: 10.00), quantity: 2)
cart.add_item(Product.new(price: 5.00), quantity: 1)
end
it 'calculates total with tax and discounts' do
cart.apply_discount_code('SAVE10') # 10% off
total = cart.total_price
# Subtotal: $25, 10% discount: $22.50, 8% tax: $24.30
expect(total).to be_within(0.01).of(24.30)
end
end
end
Resource leakage between tests occurs when tests fail to clean up files, network connections, or modified global state:
class ResourceManagementTest < Test::Unit::TestCase
def setup
@temp_files = []
@original_env = ENV.to_h.dup
end
def test_file_processing_with_cleanup
# Create temporary file
temp_file = Tempfile.new('test_data')
@temp_files << temp_file
temp_file.write("test,data\n1,2\n3,4")
temp_file.close
processor = FileProcessor.new
result = processor.process_csv(temp_file.path)
assert_equal 2, result.length
assert_equal ['1', '2'], result.first
end
def test_environment_variable_modification
ENV['TEST_MODE'] = 'true'
ENV['DEBUG_LEVEL'] = 'verbose'
config = AppConfiguration.new
assert config.test_mode?
assert_equal 'verbose', config.debug_level
end
def teardown
# Clean up temporary files
@temp_files.each do |file|
file.unlink if file.respond_to?(:unlink) && File.exist?(file.path)
end
# Restore environment variables
ENV.clear
ENV.update(@original_env)
end
end
Reference
Test::Unit Assertion Methods
Method | Parameters | Returns | Description |
---|---|---|---|
assert(condition, message = nil) |
condition (Object), message (String) |
void |
Verifies condition is truthy |
assert_equal(expected, actual, message = nil) |
expected (Object), actual (Object), message (String) |
void |
Verifies equality using == |
assert_not_equal(expected, actual, message = nil) |
expected (Object), actual (Object), message (String) |
void |
Verifies inequality |
assert_nil(object, message = nil) |
object (Object), message (String) |
void |
Verifies object is nil |
assert_not_nil(object, message = nil) |
object (Object), message (String) |
void |
Verifies object is not nil |
assert_instance_of(class, object, message = nil) |
class (Class), object (Object), message (String) |
void |
Verifies exact class match |
assert_kind_of(class, object, message = nil) |
class (Class), object (Object), message (String) |
void |
Verifies class or subclass match |
assert_respond_to(object, method, message = nil) |
object (Object), method (Symbol), message (String) |
void |
Verifies method availability |
assert_match(pattern, string, message = nil) |
pattern (Regexp), string (String), message (String) |
void |
Verifies regex pattern match |
assert_no_match(pattern, string, message = nil) |
pattern (Regexp), string (String), message (String) |
void |
Verifies no regex match |
assert_in_delta(expected, actual, delta, message = nil) |
expected (Numeric), actual (Numeric), delta (Numeric), message (String) |
void |
Verifies numeric values within delta |
assert_raise(exception_class, message = nil) { block } |
exception_class (Class), message (String), block (Proc) |
Exception |
Verifies block raises specific exception |
assert_nothing_raised(message = nil) { block } |
message (String), block (Proc) |
void |
Verifies block executes without exceptions |
assert_throws(symbol, message = nil) { block } |
symbol (Symbol), message (String), block (Proc) |
void |
Verifies block throws specific symbol |
RSpec Expectation Methods
Method | Parameters | Returns | Description |
---|---|---|---|
expect(actual).to matcher |
actual (Object), matcher (Matcher) |
void |
Positive expectation |
expect(actual).not_to matcher |
actual (Object), matcher (Matcher) |
void |
Negative expectation |
expect { block }.to matcher |
block (Proc), matcher (Matcher) |
void |
Block expectation |
expect(actual).to eq(expected) |
actual (Object), expected (Object) |
void |
Equality using == |
expect(actual).to eql(expected) |
actual (Object), expected (Object) |
void |
Equality using eql? |
expect(actual).to equal(expected) |
actual (Object), expected (Object) |
void |
Identity using equal? |
expect(actual).to be_instance_of(class) |
actual (Object), class (Class) |
void |
Exact class verification |
expect(actual).to be_a(class) |
actual (Object), class (Class) |
void |
Class or subclass verification |
expect(actual).to respond_to(method) |
actual (Object), method (Symbol) |
void |
Method availability |
expect(actual).to match(pattern) |
actual (String), pattern (Regexp) |
void |
Regex pattern matching |
expect(actual).to be_within(delta).of(expected) |
actual (Numeric), delta (Numeric), expected (Numeric) |
void |
Numeric approximation |
expect { block }.to raise_error(class, message) |
block (Proc), class (Class), message (String/Regexp) |
void |
Exception verification |
expect { block }.to change(object, method) |
block (Proc), object (Object), method (Symbol) |
void |
State change verification |
Minitest Assertion Methods
Method | Parameters | Returns | Description |
---|---|---|---|
assert(condition, message = nil) |
condition (Object), message (String) |
void |
Basic truthiness assertion |
assert_equal(expected, actual, message = nil) |
expected (Object), actual (Object), message (String) |
void |
Equality verification |
assert_in_delta(expected, actual, delta = 0.001, message = nil) |
expected (Float), actual (Float), delta (Float), message (String) |
void |
Float comparison with tolerance |
assert_includes(collection, object, message = nil) |
collection (Enumerable), object (Object), message (String) |
void |
Collection membership |
assert_match(pattern, string, message = nil) |
pattern (Regexp), string (String), message (String) |
void |
Pattern matching |
assert_operator(object1, operator, object2, message = nil) |
object1 (Object), operator (Symbol), object2 (Object), message (String) |
void |
Operator-based comparison |
assert_predicate(object, predicate, message = nil) |
object (Object), predicate (Symbol), message (String) |
void |
Predicate method verification |
assert_raises(exception_class, message = nil) { block } |
exception_class (Class), message (String), block (Proc) |
Exception |
Exception verification |
refute(condition, message = nil) |
condition (Object), message (String) |
void |
Negative assertion |
refute_equal(expected, actual, message = nil) |
expected (Object), actual (Object), message (String) |
void |
Inequality verification |
Framework Configuration Options
Framework | Configuration Method | Purpose |
---|---|---|
Test::Unit | Test::Unit.at_start { block } |
Code execution before test suite |
Test::Unit | Test::Unit.at_exit { block } |
Code execution after test suite |
RSpec | `RSpec.configure { | config |
RSpec | config.before(:each) { block } |
Before each test execution |
RSpec | config.after(:each) { block } |
After each test execution |
RSpec | `config.around(:each) { | example |
Minitest | Minitest.after_run { block } |
Post-suite execution hook |
Common Test Patterns
Pattern | Framework | Implementation |
---|---|---|
Setup/Teardown | Test::Unit | def setup / def teardown methods |
Lazy Loading | RSpec | let(:name) { expression } |
Eager Loading | RSpec | let!(:name) { expression } |
Shared Examples | RSpec | shared_examples 'name' / include_examples |
Parameterized Tests | All | Dynamic method definition with data arrays |
Mock Expectations | RSpec | expect(object).to receive(:method) |
Stub Behavior | RSpec | allow(object).to receive(:method).and_return(value) |
Exception Testing | Test::Unit | assert_raise(ExceptionClass) { block } |
State Change Testing | RSpec | expect { block }.to change(object, :method) |
Test Execution Commands
Framework | Command | Purpose |
---|---|---|
Test::Unit | ruby test/test_file.rb |
Run single test file |
Test::Unit | ruby -Itest test/test_*.rb |
Run all tests in directory |
RSpec | rspec spec/file_spec.rb |
Run single spec file |
RSpec | rspec spec/ |
Run all specs in directory |
RSpec | rspec --format documentation |
Detailed output format |
Minitest | ruby -Itest test/test_file.rb |
Run single test file |
Minitest | rake test |
Run all tests via Rake |
All | --verbose |
Detailed test execution output |