Compatibility and troubleshooting
August 10, 2026 · View on GitHub
Ruby and framework compatibility notes, common problems, and upgrade guidance.
Part of the SimpleCov documentation.
Compatibility and troubleshooting
Ruby version compatibility
SimpleCov is built in Continuous Integration on Ruby 3.2+ and JRuby 10+. On CRuby, every coverage criterion described above is available on the supported versions, with one exception: eval coverage requires CRuby 3.2+.
JRuby
On JRuby, only line coverage is available — branch, method, oneshot-line, and eval coverage rely on features of
CRuby's Coverage library that JRuby doesn't implement. SimpleCov detects this automatically: the bundled strict
profile, for instance, enforces only line coverage at 100% on JRuby instead of failing to load.
To get accurate line numbers in coverage results, JRuby needs its full backtrace enabled. Pass JRUBY_OPTS="--debug",
or create a .jrubyrc with debug.fullTrace=true.
Notes on specific frameworks and test utilities
Some frameworks and tools have quirks worth knowing about when using SimpleCov:
| Framework | Notes | Issue |
|---|---|---|
| parallel_tests | As of 0.8.0, SimpleCov should correctly recognize parallel_tests and supplement your test suite names with their corresponding test env numbers. SimpleCov locks the resultset cache while merging, ensuring no race conditions occur when results are merged. | #64 & #185 |
| knapsack_pro |
To make SimpleCov work with Knapsack Pro Queue Mode to split tests in parallel on CI jobs you need to provide CI node index number to the SimpleCov.command_name in KnapsackPro::Hooks::Queue.before_queue hook.
|
Tip |
| RubyMine | The RubyMine IDE has built-in support for SimpleCov's coverage reports, though you might need to explicitly set the output root using `SimpleCov.root('foo/bar/baz')` | #95 |
| Spork | Because of how Spork works internally (using preforking), there used to be trouble when using SimpleCov with it, but that has apparently been resolved with a specific configuration strategy. See this comment. | #42 |
| Spring | See section below. | #381 |
| Test/Unit |
Test Unit 2 used to mess with ARGV, leading to a failure to detect the
test process name in SimpleCov. test-unit releases 2.4.3+
(Dec 11th, 2011) should have this problem resolved.
|
#45 & test-unit/test-unit#12 |
Using Spring with SimpleCov
If you use Spring to speed up test runs, SimpleCov often misreports coverage with the default config due to an eager-loading issue. There are a few fixes.
One solution is to explicitly call eager
load in your test_helper.rb /
spec_helper.rb after calling SimpleCov.start:
require 'simplecov'
SimpleCov.start 'rails'
Rails.application.eager_load!
Alternatively, disable Spring while running SimpleCov:
DISABLE_SPRING=1 rake test
Or remove gem 'spring' from your Gemfile.
Different coverage between local and CI
Rails generates config/environments/test.rb with config.eager_load = ENV["CI"].present? (Rails 7+), so CI eagerly
loads every file in app/ while your local run does not. The two environments then report different file sets and
different totals from the same suite. Two ways to make the report deterministic:
- Set
config.eager_load = trueeverywhere intest.rb(slower locally, but matches CI — and matches what users actually see in production). - Stick with the
railsprofile, which folds{app,lib}/**/*.rbinto the report at 0% on every run regardless ofeager_load. (The profile resolves the glob relative toSimpleCov.root, not the test runner's cwd.) Outside the profile, the equivalent iscover "{app,lib}/**/*.rb"— see the legacy-API migration table for the relationship with the oldertrack_files.
Missing coverage
The most common problem is that SimpleCov isn't required and started before everything else. To track coverage for your whole application, SimpleCov must come first so that it (and the underlying Coverage library) can track files as they're loaded and used.
If coverage is missing for some code, a simple trick is to add a puts inside that file and another right after
SimpleCov.start, then check the order they print in:
# my_code.rb
class MyCode
puts "MyCode is being loaded!"
def my_method
# ...
end
end
# spec_helper.rb / rails_helper.rb / test_helper.rb / .simplecov — whatever
SimpleCov.start
puts "SimpleCov started successfully!"
If you see this order, you're good:
SimpleCov started successfully!
MyCode is being loaded!
If MyCode is being loaded! prints first, the file was loaded before SimpleCov started — that's your problem.
Upgrading from 0.x
Four methods that had been deprecated for a decade or more were removed in 1.0. Each had a one-to-one rename:
| Removed | Use instead |
|---|---|
SimpleCov::Filter#passes? | SimpleCov::Filter#matches? |
SimpleCov.adapters | SimpleCov.profiles |
SimpleCov.load_adapter('rails') | SimpleCov.load_profile('rails') |
SimpleCov::Formatter::MultiFormatter[] | SimpleCov::Formatter::MultiFormatter.new |
If a custom filter still defines passes?, rename the method to matches? — the signature and semantics are identical.