Overview
SimpleCov analyzes Ruby code execution during test runs to generate coverage reports. The gem instruments Ruby code at runtime, tracking which lines execute during test suite runs and producing detailed coverage statistics. SimpleCov operates by hooking into Ruby's coverage API and wrapping the standard library's Coverage module with additional reporting capabilities.
The gem centers around the SimpleCov
module, which provides the primary interface for configuration and report generation. The SimpleCov::Result
class represents coverage data for a complete test run, while SimpleCov::SourceFile
objects contain line-by-line coverage information for individual files. Coverage collection happens automatically once SimpleCov starts, requiring no modification to application code.
SimpleCov supports multiple output formats including HTML, JSON, and terminal reports. The HTML formatter generates interactive reports showing covered and uncovered lines with color-coded highlighting. JSON output enables integration with external tools and continuous integration systems.
# Basic setup in test helper
require 'simplecov'
SimpleCov.start
# Configure coverage thresholds
SimpleCov.minimum_coverage 90
SimpleCov.minimum_coverage_by_file 80
The gem tracks line coverage by default but supports branch coverage analysis on Ruby 2.5+. Branch coverage provides deeper insight into conditional logic execution, tracking whether both sides of if/else statements execute during testing. SimpleCov integrates with major testing frameworks including RSpec, Test::Unit, and Minitest without requiring framework-specific configuration.
Coverage data persists between test runs in the .simplecov_resultset.json
file, enabling merging of results from multiple test processes or CI matrix builds. This persistence allows SimpleCov to generate accurate coverage reports even when tests run in parallel or across different environments.
Basic Usage
SimpleCov activation requires calling SimpleCov.start
before loading application code, typically in a test helper file. The gem begins tracking coverage immediately after start, so placement matters for accurate results.
# spec/spec_helper.rb or test/test_helper.rb
require 'simplecov'
SimpleCov.start
# Load application after starting SimpleCov
require_relative '../lib/my_app'
The start
method accepts configuration blocks for immediate setup. Common configurations include specifying coverage directories, setting minimum thresholds, and selecting formatters:
SimpleCov.start do
add_filter '/spec/'
add_filter '/test/'
minimum_coverage 85
minimum_coverage_by_file 75
formatter SimpleCov::Formatter::MultiFormatter.new([
SimpleCov::Formatter::HTMLFormatter,
SimpleCov::Formatter::SimpleFormatter
])
end
Coverage collection happens transparently during test execution. SimpleCov tracks every line that executes, building internal coverage maps without impacting application functionality. After test completion, SimpleCov generates reports based on collected data.
Filters exclude specific files or directories from coverage analysis. The add_filter
method accepts strings, regular expressions, or blocks for flexible filtering:
# Exclude directories
SimpleCov.add_filter '/vendor/'
SimpleCov.add_filter '/gems/'
# Pattern-based filtering
SimpleCov.add_filter /\.proto$/
# Block-based filtering
SimpleCov.add_filter do |src_file|
src_file.lines.count < 10
end
Groups organize coverage results into logical sections for reporting. Groups help separate different application components in generated reports:
SimpleCov.add_group 'Models', 'app/models'
SimpleCov.add_group 'Controllers', 'app/controllers'
SimpleCov.add_group 'Libraries', 'lib'
SimpleCov.add_group 'Long files' do |src_file|
src_file.lines.count > 100
end
Coverage thresholds enforce minimum coverage requirements. SimpleCov exits with non-zero status codes when coverage falls below configured thresholds, enabling CI integration:
SimpleCov.minimum_coverage 90
SimpleCov.minimum_coverage_by_file 80
# Different thresholds for different groups
SimpleCov.minimum_coverage({
'Models' => 95,
'Controllers' => 85,
'Libraries' => 75
})
Advanced Usage
SimpleCov provides sophisticated configuration options for complex project requirements. The configure
block enables detailed customization beyond basic start parameters:
SimpleCov.configure do
load_profile 'rails'
coverage_dir 'coverage_reports'
command_name 'RSpec'
merge_timeout 600
add_filter do |src_file|
!src_file.filename.match(/\/app\//)
end
track_files '{app,lib}/**/*.rb'
formatter SimpleCov::Formatter::MultiFormatter.new([
SimpleCov::Formatter::HTMLFormatter,
SimpleCov::Formatter::JSONFormatter,
SimpleCov::Formatter::LcovFormatter
])
end
Profiles provide predefined configurations for common project types. The Rails profile automatically excludes typical Rails directories that shouldn't count toward coverage:
SimpleCov.start 'rails' do
minimum_coverage 90
add_group 'Policies', 'app/policies'
add_group 'Serializers', 'app/serializers'
add_group 'Workers', 'app/workers'
end
Branch coverage analysis provides deeper insight into conditional logic execution. Enable branch coverage to track whether all branches of if/else statements, case/when blocks, and boolean expressions execute:
SimpleCov.start do
enable_coverage :branch
primary_coverage :branch
minimum_coverage({ line: 90, branch: 80 })
end
Result merging combines coverage data from multiple test runs or parallel processes. Configure merge timeout and result expiration to handle various testing scenarios:
SimpleCov.configure do
merge_timeout 3600 # 1 hour
# Custom merge logic
SimpleCov.merge_result_sets do |result_sets|
result_sets.select { |rs| rs['timestamp'] > (Time.now - 3600) }
end
end
Custom formatters enable specialized output formats. Create formatter classes implementing the format
method:
class TeamCityFormatter
def format(result)
result.groups.each do |name, files|
coverage = (files.covered_percent * 100).round(2)
puts "##teamcity[buildStatisticValue key='CodeCoverageAbs#{name}' value='#{coverage}']"
end
end
end
SimpleCov.formatter = TeamCityFormatter
The at_exit
hook controls when SimpleCov generates reports. Customize exit behavior for non-standard test execution patterns:
SimpleCov.at_exit do |result|
if ENV['COVERAGE_REPORT'] == 'true'
result.format!
end
puts "Coverage: #{result.covered_percent.round(2)}%"
exit 1 if result.covered_percent < 85
end
File tracking configuration determines which files appear in coverage reports even when never loaded. Track files enables coverage reporting for completely unused files:
SimpleCov.track_files 'lib/**/*.rb'
SimpleCov.track_files '{app,lib}/**/*.{rb,rake}'
# Conditional tracking
SimpleCov.track_files do
Dir.glob('lib/**/*.rb').reject { |f| f.match(/deprecated/) }
end
Testing Strategies
SimpleCov integration varies across testing frameworks but follows consistent patterns. Configure SimpleCov before loading application code and after requiring the testing framework:
# RSpec setup in spec/spec_helper.rb
require 'rspec'
require 'simplecov'
SimpleCov.start 'rails' do
add_filter '/spec/'
minimum_coverage 85
end
RSpec.configure do |config|
config.filter_run_when_matching :focus
end
Parallel testing requires special consideration for coverage merging. SimpleCov automatically handles parallel test execution when using tools like parallel_tests
:
# Setup for parallel_tests gem
SimpleCov.start do
if ENV['TEST_ENV_NUMBER']
SimpleCov.command_name "RSpec-#{ENV['TEST_ENV_NUMBER']}"
end
merge_timeout 600
end
Test coverage analysis benefits from strategic test organization. Structure tests to exercise different code paths and edge cases:
# Example of comprehensive test coverage
describe UserRegistration do
let(:valid_params) { { email: 'user@example.com', password: 'secret123' } }
context 'with valid parameters' do
it 'creates user successfully' do
registration = UserRegistration.new(valid_params)
expect(registration.call).to be_success
end
end
context 'with invalid email' do
it 'fails with validation error' do
params = valid_params.merge(email: 'invalid')
registration = UserRegistration.new(params)
expect(registration.call).to be_failure
end
end
context 'with duplicate email' do
before { create(:user, email: valid_params[:email]) }
it 'fails with uniqueness error' do
registration = UserRegistration.new(valid_params)
expect(registration.call).to be_failure
end
end
end
Branch coverage testing requires exercising all conditional paths. Structure tests to cover both positive and negative branches:
describe PaymentProcessor do
describe '#process' do
context 'when payment succeeds' do
before { stub_payment_gateway_success }
it 'returns success result' do
result = processor.process(payment_data)
expect(result).to be_success
end
end
context 'when payment fails due to insufficient funds' do
before { stub_payment_gateway_failure(:insufficient_funds) }
it 'returns failure with specific error' do
result = processor.process(payment_data)
expect(result).to be_failure
expect(result.error_code).to eq(:insufficient_funds)
end
end
context 'when gateway times out' do
before { stub_payment_gateway_timeout }
it 'raises timeout exception' do
expect { processor.process(payment_data) }.to raise_error(Timeout::Error)
end
end
end
end
Coverage-driven test writing identifies gaps in test coverage. Use SimpleCov reports to find untested code paths and write targeted tests:
# After running tests, check coverage report for missed lines
# Then write tests for uncovered branches
describe CacheManager do
describe '#fetch' do
context 'when cache hit' do
before { cache.write('key', 'cached_value') }
it 'returns cached value without calling block' do
result = manager.fetch('key') { expensive_operation }
expect(result).to eq('cached_value')
expect(expensive_operation).not_to have_been_called
end
end
context 'when cache miss' do
it 'calls block and caches result' do
result = manager.fetch('key') { 'fresh_value' }
expect(result).to eq('fresh_value')
expect(cache.read('key')).to eq('fresh_value')
end
end
context 'when cache raises exception' do
before { allow(cache).to receive(:read).and_raise(Redis::TimeoutError) }
it 'falls back to block execution' do
result = manager.fetch('key') { 'fallback_value' }
expect(result).to eq('fallback_value')
end
end
end
end
Production Patterns
SimpleCov deployment in production environments requires careful configuration to minimize performance impact. Production coverage analysis helps identify dead code and actual usage patterns in live applications:
# Production coverage setup
SimpleCov.start do
if Rails.env.production? && ENV['ENABLE_COVERAGE']
coverage_dir 'tmp/coverage'
add_filter '/vendor/'
add_filter '/config/'
add_filter do |src_file|
src_file.filename.match?(/\/db\/migrate\//)
end
formatter SimpleCov::Formatter::JSONFormatter
# Minimize memory usage
merge_timeout 60
maximum_coverage_drop 5
end
end
Continuous Integration integration enables automated coverage reporting and threshold enforcement. Configure CI pipelines to generate and publish coverage reports:
# GitHub Actions example
- name: Run tests with coverage
run: |
bundle exec rspec
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
with:
file: ./coverage/.resultset.json
- name: Check coverage threshold
run: |
if [ $(cat coverage/.last_run.json | jq '.result.covered_percent') -lt 85 ]; then
echo "Coverage below threshold"
exit 1
fi
Docker environments require special handling for coverage data persistence. Mount coverage directories as volumes to preserve reports across container restarts:
# Dockerfile additions for coverage
RUN mkdir -p /app/coverage
VOLUME ["/app/coverage"]
# In docker-compose.yml
services:
app:
volumes:
- ./coverage:/app/coverage
environment:
- COVERAGE=true
Performance monitoring becomes critical in production coverage scenarios. SimpleCov adds overhead to application execution, requiring monitoring to prevent performance degradation:
SimpleCov.configure do
if Rails.env.production?
# Sample coverage to reduce overhead
if rand < 0.1 # Cover 10% of requests
start
end
# Custom at_exit to handle web server shutdown
at_exit do |result|
# Save results asynchronously
Thread.new do
result.format!
upload_to_storage(result)
end
end
end
end
Multi-server deployments require coverage result aggregation. Implement strategies to combine coverage data from multiple application instances:
class CoverageAggregator
def self.merge_server_results
results = []
Dir.glob('/shared/coverage/server_*/resultset.json').each do |file|
results << JSON.parse(File.read(file))
end
merged_result = SimpleCov::ResultMerger.merge_results(*results)
File.write('/shared/coverage/merged_resultset.json', merged_result.to_json)
SimpleCov::Formatter::HTMLFormatter.new.format(merged_result)
end
end
Coverage reporting automation helps maintain code quality standards. Implement automated reports that integrate with project management and notification systems:
class CoverageReporter
def self.generate_daily_report
result = SimpleCov::Result.from_hash(
JSON.parse(File.read('.simplecov_resultset.json'))
)
report = {
date: Date.current,
overall_coverage: result.covered_percent.round(2),
file_count: result.files.count,
groups: result.groups.transform_values { |files| files.covered_percent.round(2) },
files_below_threshold: result.files.select { |f| f.covered_percent < 80 }
}
SlackNotifier.ping(format_coverage_message(report))
ProjectTracker.create_issue(report) if report[:overall_coverage] < 85
end
private
def self.format_coverage_message(report)
"Daily Coverage Report: #{report[:overall_coverage]}% overall, " \
"#{report[:files_below_threshold].count} files below threshold"
end
end
Common Pitfalls
SimpleCov activation timing creates the most frequent configuration errors. Starting SimpleCov after loading application code results in zero coverage because the gem cannot instrument already-loaded files:
# INCORRECT: SimpleCov starts after loading app
require_relative '../lib/my_app'
require 'simplecov'
SimpleCov.start
# CORRECT: SimpleCov starts before loading app
require 'simplecov'
SimpleCov.start
require_relative '../lib/my_app'
Exit hook conflicts occur when multiple gems override Ruby's at_exit
behavior. SimpleCov relies on at_exit
hooks to generate final reports, but other gems can interfere:
# Problematic interaction with other gems
require 'simplecov'
require 'some_gem_that_uses_at_exit'
SimpleCov.start
# Solution: Force SimpleCov report generation
SimpleCov.at_exit do |result|
result.format! unless ENV['NO_COVERAGE']
end
Filter misconfiguration leads to unexpected coverage results. Overly broad filters can exclude important application code, while insufficient filtering includes test files in coverage calculations:
# Problematic: Too broad, excludes important files
SimpleCov.add_filter '/app/' # Excludes entire app directory
# Problematic: Doesn't exclude test files
SimpleCov.add_filter '/spec/models/' # Only excludes model specs
# Better: Specific exclusions
SimpleCov.add_filter '/spec/'
SimpleCov.add_filter '/test/'
SimpleCov.add_filter '/features/'
SimpleCov.add_filter do |src_file|
src_file.filename.match?(/\/config\//)
end
Branch coverage interpretation requires understanding Ruby's branch tracking behavior. Ruby counts ternary operators, logical operators, and rescue clauses as branches:
# This code has more branches than obvious
def process_user(user)
return unless user&.active? # Branch 1: &&, Branch 2: unless
status = user.premium? ? 'premium' : 'standard' # Branch 3: ternary
begin
send_notification(user, status)
rescue NotificationError
log_error("Failed to notify user #{user.id}") # Branch 4: rescue
end
user.admin? || user.moderator? # Branch 5: ||
end
Parallel test execution creates result merging challenges. SimpleCov may generate incomplete coverage reports when parallel processes don't properly coordinate result sharing:
# Configure proper parallel testing support
SimpleCov.start do
# Use unique command names for each process
if ENV['TEST_ENV_NUMBER'].present?
command_name "RSpec-Process-#{ENV['TEST_ENV_NUMBER']}"
end
# Extend merge timeout for slow test suites
merge_timeout 600
# Ensure all processes wait for result merging
SimpleCov.use_merging true
end
Memory consumption grows significantly in large codebases with comprehensive coverage tracking. SimpleCov maintains detailed line-by-line data for every tracked file:
# Reduce memory usage for large applications
SimpleCov.start do
# Exclude large generated files
add_filter do |src_file|
src_file.lines.count > 1000 && src_file.filename.match?(/generated/)
end
# Use JSON formatter instead of HTML for lower memory usage
formatter SimpleCov::Formatter::JSONFormatter
# Limit tracked files
track_files '{app,lib}/**/*.rb'
end
Coverage threshold failures in CI environments often result from environmental differences between local and CI test execution. Different Ruby versions, gem versions, or test data can cause coverage variations:
# Make thresholds environment-aware
SimpleCov.configure do
if ENV['CI']
minimum_coverage 88 # Slightly lower for CI variability
maximum_coverage_drop 3
else
minimum_coverage 90 # Stricter for local development
end
# Allow coverage drops during refactoring
if ENV['REFACTORING_BRANCH']
refuse_coverage_drop false
end
end
Report generation timing issues occur when applications exit before SimpleCov completes report writing. This happens frequently in containerized environments or applications with custom exit handling:
# Ensure report generation completes
SimpleCov.at_exit do |result|
begin
result.format!
rescue => e
warn "SimpleCov report generation failed: #{e.message}"
warn e.backtrace.join("\n")
end
end
# For applications with custom exit handling
class Application
def shutdown
SimpleCov.result.format! if defined?(SimpleCov)
super
end
end
Reference
Core Methods
Method | Parameters | Returns | Description |
---|---|---|---|
SimpleCov.start |
block (optional) | nil |
Begins coverage tracking and accepts configuration block |
SimpleCov.configure |
block | nil |
Sets configuration options without starting coverage |
SimpleCov.result |
- | SimpleCov::Result |
Returns current coverage result object |
SimpleCov.running |
- | Boolean |
Indicates whether coverage tracking is active |
SimpleCov.clear |
- | nil |
Resets all coverage data and configuration |
SimpleCov.merge_timeout |
timeout (Integer) | Integer |
Sets/gets result merge timeout in seconds |
Configuration Methods
Method | Parameters | Returns | Description |
---|---|---|---|
add_filter |
pattern (String/Regex/Proc) | nil |
Excludes files from coverage analysis |
add_group |
name (String), pattern | nil |
Creates named group for organizing results |
minimum_coverage |
threshold (Numeric/Hash) | Numeric/Hash |
Sets minimum coverage requirements |
minimum_coverage_by_file |
threshold (Numeric) | Numeric |
Sets per-file minimum coverage |
maximum_coverage_drop |
drop (Numeric) | Numeric |
Sets maximum allowed coverage decrease |
coverage_dir |
path (String) | String |
Sets output directory for reports |
command_name |
name (String) | String |
Sets identifier for result merging |
Coverage Control
Method | Parameters | Returns | Description |
---|---|---|---|
enable_coverage |
type (Symbol) | nil |
Enables specific coverage types (:line, :branch) |
disable_coverage |
type (Symbol) | nil |
Disables specific coverage types |
primary_coverage |
type (Symbol) | Symbol |
Sets primary coverage type for thresholds |
track_files |
pattern (String/Proc) | nil |
Includes files in coverage even when not loaded |
refuse_coverage_drop |
enabled (Boolean) | Boolean |
Controls whether coverage drops cause failures |
Result Methods
Method | Parameters | Returns | Description |
---|---|---|---|
result.covered_percent |
- | Float |
Overall coverage percentage |
result.covered_lines |
- | Integer |
Total number of covered lines |
result.total_lines |
- | Integer |
Total number of trackable lines |
result.files |
- | Array<SimpleCov::SourceFile> |
All tracked source files |
result.groups |
- | Hash<String, Array> |
Grouped files by configured groups |
result.command_name |
- | String |
Command identifier for this result |
result.format! |
- | nil |
Generates reports using configured formatters |
Built-in Formatters
Formatter | Output | Description |
---|---|---|
SimpleCov::Formatter::HTMLFormatter |
HTML files | Interactive web-based coverage reports |
SimpleCov::Formatter::SimpleFormatter |
Terminal text | Basic text output with coverage percentages |
SimpleCov::Formatter::JSONFormatter |
JSON file | Machine-readable coverage data |
SimpleCov::Formatter::LcovFormatter |
LCOV format | Compatible with LCOV tools and services |
SimpleCov::Formatter::MultiFormatter |
Multiple | Combines multiple formatters |
Configuration Profiles
Profile | Filters | Groups | Description |
---|---|---|---|
rails |
/spec/ , /test/ , /features/ , /autotest/ , /cucumber/ , /db/ , /config/ , /vendor/ |
Controllers, Models, Helpers, Libraries, Plugins | Rails application defaults |
test_frameworks |
/spec/ , /test/ , /features/ , /autotest/ , /cucumber/ |
- | Basic test file exclusions |
Environment Variables
Variable | Type | Description |
---|---|---|
COVERAGE |
Boolean | Enables/disables SimpleCov when set to 'true' |
SIMPLECOV_COMMAND_NAME |
String | Sets command name for result identification |
TEST_ENV_NUMBER |
String | Process identifier for parallel testing |
CI |
Boolean | Indicates continuous integration environment |
Exit Codes
Code | Condition |
---|---|
0 |
Coverage meets all configured thresholds |
1 |
Coverage below minimum threshold |
2 |
Coverage drop exceeds maximum allowed drop |
3 |
SimpleCov configuration error |
Coverage Types
Type | Ruby Version | Description |
---|---|---|
:line |
1.9+ | Tracks line execution coverage |
:branch |
2.5+ | Tracks conditional branch coverage |
:method |
2.6+ | Tracks method call coverage |
Common Filter Patterns
# Standard exclusions
add_filter '/spec/'
add_filter '/test/'
add_filter '/vendor/'
add_filter '/config/'
# Pattern-based exclusions
add_filter /\.proto$/
add_filter /_pb\.rb$/
add_filter /\/db\/migrate\//
# Size-based exclusions
add_filter { |src| src.lines.count < 5 }
add_filter { |src| src.filename.match?(/generated/) }