SimpleCov is a code coverage analysis tool for Ruby applications that provides detailed insights into which parts of your codebase are executed during test runs. Its comprehensive reporting capabilities and seamless integration with popular testing frameworks have made it an essential tool for maintaining high-quality, well-tested applications.
This guide will walk you through setting up code coverage tracking for your Ruby application using SimpleCov. You'll discover how to take advantage of the gem's extensive features and tailor them to create an optimal configuration that matches your project's specific requirements.
Prerequisites
Before moving forward with this guide, make sure you have Ruby installed on your development machine (version 2.7 or higher is recommended). This tutorial also assumes you're comfortable with Ruby's basic testing concepts and have experience with at least one testing framework like RSpec, Minitest, or Cucumber.
Getting started with SimpleCov
To get the most value from this tutorial, create a fresh Ruby project where you can experiment with the concepts we'll be exploring. Begin by setting up a new project using these commands:
mkdir simplecov-demo && cd simplecov-demo
bundle init
Next, add SimpleCov to your Gemfile by running this command:
bundle add simplecov --group development,test
The examples in this guide are compatible with SimpleCov 0.22.x, which is the latest major version at the time of writing. Create a new lib/calculator.rb file in your project directory with this simple class:
mkdir lib
class Calculator
  def add(a, b)
    a + b
  end
  def subtract(a, b)
    a - b
  end
  def multiply(a, b)
    a * b
  end
  def divide(a, b)
    raise ArgumentError, 'Cannot divide by zero' if b.zero?
    a / b
  end
end
Now create a test file at spec/calculator_spec.rb:
mkdir spec
require 'simplecov'
SimpleCov.start
require_relative '../lib/calculator'
RSpec.describe Calculator do
  let(:calculator) { Calculator.new }
  describe '#add' do
    it 'returns the sum of two numbers' do
      expect(calculator.add(2, 3)).to eq(5)
    end
  end
  describe '#subtract' do
    it 'returns the difference between two numbers' do
      expect(calculator.subtract(5, 3)).to eq(2)
    end
  end
end
This example demonstrates the minimum configuration needed to start tracking coverage. The critical part is requiring SimpleCov and calling SimpleCov.start before loading any application code. This ensures SimpleCov can properly instrument your code and track execution.
Before running the tests, install RSpec:
bundle add rspec
bundle exec rspec --init
Now execute your test suite:
bundle exec rspec
After the tests complete, you'll see coverage information printed to your terminal:
2 examples, 0 failures
Coverage report generated for RSpec to /path/to/simplecov-demo/coverage.
Line Coverage: 70.0% (7 / 10)
SimpleCov automatically generates an HTML report in the coverage directory. Open coverage/index.html in your browser to see a detailed breakdown of your code coverage:
The report shows that 70% of the relevant lines in your code were executed during the test run. The summary table displays each file along with its coverage percentage, total lines, and the number of lines covered versus missed.
Clicking on lib/calculator.rb takes you to a detailed view that reveals exactly which lines were covered and which weren't:
The numbers displayed next to each line indicate how many times that line executed during the test run. Lines that executed at least once show their hit count, while untested lines are clearly marked.
Notice that the multiply and divide methods lack coverage on their implementation lines, clearly identifying the untested code paths. This visual feedback makes it straightforward to spot gaps in your test coverage and prioritize which methods need additional tests.
Configuring SimpleCov properly
While the basic setup works, there's a better way to configure SimpleCov that avoids cluttering your test files with coverage setup code. The recommended approach is creating a dedicated .simplecov file in your project root:
SimpleCov.start do
  add_filter '/spec/'
  add_filter '/test/'
end
This configuration file is automatically loaded by SimpleCov when it starts. With this setup, you can simplify your spec/spec_helper.rb file to just require SimpleCov without explicit configuration:
require 'simplecov'
SimpleCov.start
# Rest of your RSpec configuration
RSpec.configure do |config|
  config.expect_with :rspec do |expectations|
    expectations.include_chain_clauses_in_custom_matcher_descriptions = true
  end
end
Then update your spec/calculator_spec.rb to use the spec helper instead of requiring SimpleCov directly:
require_relative 'spec_helper'
require_relative '../lib/calculator'
RSpec.describe Calculator do
  let(:calculator) { Calculator.new }
  describe '#add' do
    it 'returns the sum of two numbers' do
      expect(calculator.add(2, 3)).to eq(5)
    end
  end
  describe '#subtract' do
    it 'returns the difference between two numbers' do
      expect(calculator.subtract(5, 3)).to eq(2)
    end
  end
end
Run your tests again to verify everything still works:
bundle exec rspec
2 examples, 0 failures
Coverage report generated for RSpec to /path/to/simplecov-demo/coverage.
Line Coverage: 70.0% (7 / 10)
This approach keeps configuration centralized and makes it easier to maintain consistent coverage settings across your entire test suite. The .simplecov file is also where you'll add most of your customizations as your needs evolve.
Understanding coverage metrics
SimpleCov tracks several types of coverage metrics that provide different insights into how thoroughly your code is tested. The default metric is line coverage, which measures the percentage of executable lines that ran during your test suite.
Line coverage
Line coverage is the most straightforward metric. It measures whether each line of code was executed at least once during the test run. When you see a line marked as covered in the HTML report, it means at least one test caused that line to execute:
def process_payment(amount)
  return false if amount <= 0  # Line 1
  charge_card(amount)          # Line 2
  send_receipt                 # Line 3
  true                         # Line 4
end
If your tests only call process_payment with positive amounts, Line 1 executes but the early return doesn't trigger. Lines 2-4 all execute normally. This gives you 100% line coverage even though the error condition isn't fully tested.
Branch coverage
Branch coverage goes deeper than line coverage by tracking whether both sides of conditional statements were executed. This catches situations where a line runs but not all possible paths through that line were tested:
def discount_price(price, member)
  member ? price * 0.9 : price
end
Testing only with member = true gives 100% line coverage but only 50% branch coverage because the false branch never executes. Branch coverage helps identify these hidden gaps in your test suite.
To enable branch coverage in SimpleCov, update your .simplecov file:
SimpleCov.start do
  enable_coverage :branch
  add_filter '/spec/'
  add_filter '/test/'
end
Run your tests again to see branch coverage metrics:
bundle exec rspec
2 examples, 0 failures
Coverage report generated for RSpec to /path/to/simplecov-demo/coverage.
Line Coverage: 70.0% (7 / 10)
Branch Coverage: 0.0% (0 / 2)
Branch coverage typically runs lower than line coverage because it's more demanding. You can set separate thresholds for each metric to acknowledge this reality while still maintaining high standards.
Setting coverage thresholds
Establishing minimum coverage requirements prevents coverage from degrading over time as new code gets added. SimpleCov makes it straightforward to enforce these standards by failing your test suite when coverage drops below acceptable levels.
Add coverage thresholds to your .simplecov configuration:
SimpleCov.start do
  enable_coverage :branch
  add_filter '/spec/'
  add_filter '/test/'
  minimum_coverage line: 90, branch: 80
end
When coverage falls below the specified thresholds, SimpleCov exits with a non-zero status code and prints a clear error message:
bundle exec rspec
2 examples, 0 failures
Coverage report generated for RSpec to /path/to/simplecov-demo/coverage.
Line Coverage: 70.0% (7 / 10)
Branch Coverage: 0.0% (0 / 2)
Line coverage (70.00%) is below the expected minimum coverage (90.00%).
Branch coverage (0.00%) is below the expected minimum coverage (80.00%).
SimpleCov failed with exit 2 due to a coverage related error
This automatic failure is particularly valuable in continuous integration pipelines, where it can block merges of poorly tested code. Your CI system will catch the failure and prevent the code from reaching production.
You can also track coverage changes by comparing against the last run:
SimpleCov.start do
  enable_coverage :branch
  add_filter '/spec/'
  add_filter '/test/'
  minimum_coverage line: 90, branch: 80
  maximum_coverage_drop 2
end
The maximum_coverage_drop setting fails the build if coverage decreases by more than 2% compared to the previous run. This catches situations where new code brings down overall coverage even if the total remains above your minimum threshold.
Filtering files from coverage reports
Not all files in your project need coverage tracking. Test files, configuration files, and generated code typically fall outside the scope of meaningful coverage analysis. SimpleCov provides flexible filtering options to exclude these files from your reports.
The add_filter method accepts string patterns or regular expressions to match files you want to exclude:
SimpleCov.start do
  enable_coverage :branch
  # Exclude test directories
  add_filter '/spec/'
  add_filter '/test/'
  # Exclude configuration files
  add_filter '/config/'
  # Exclude database migrations
  add_filter '/db/migrate/'
  # Exclude vendor directory
  add_filter '/vendor/'
  minimum_coverage line: 90, branch: 80
end
Or you can also use block syntax for more complex filtering logic:
SimpleCov.start do
  enable_coverage :branch
  add_filter '/spec/'
  add_filter '/test/'
  add_filter do |source_file|
    # Exclude files with fewer than 5 lines
    source_file.lines.count < 5
  end
  minimum_coverage line: 90, branch: 80
end
For Rails applications, SimpleCov provides a convenient preset that automatically excludes common directories:
SimpleCov.start 'rails' do
  enable_coverage :branch
  # Additional custom filters
  add_filter '/app/admin/'
  minimum_coverage line: 90, branch: 80
end
The 'rails' preset excludes test/, spec/, config/, and other standard Rails directories, saving you from manually configuring each filter. The coverage report will now only includes files that pass your filter criteria, giving you a cleaner view of your application's actual test coverage.
Final thoughts
SimpleCov makes it easy to measure, understand, and improve your test coverage in Ruby applications. By setting it up once, you gain clear insights into which parts of your code are tested and which need more attention.
With features like branch coverage, coverage thresholds, and customizable filters, SimpleCov helps you maintain reliable, high-quality code and prevents untested code from slipping into production.
