undercover warns about methods, classes and blocks that were changed without tests, to help you easily find untested code and reduce the number of bugs. It does so by analysing data from git diffs, code structure and SimpleCov coverage reports
See the codeundercover warns about methods, classes and blocks that were changed without tests, to help you easily find untested code and reduce the number of bugs. It does so by analysing data from git diffs, code structure and SimpleCov coverage reports.
Works with any Ruby CI pipeline as well as locally as a CLI.
A sample output of undercover ran before a commit may look like this:

And like this, given that specs were added:

Add this line to your application's Gemfile:
gem 'undercover'
And then execute:
$ bundle
Or install it yourself as:
$ gem install undercover
[!NOTE]
undercoverrequires SimpleCov >= 1.0 (and Ruby >= 3.2). For older SimpleCov (0.x) or Ruby setups, pinundercoverto~> 0.8.0.
To make your specs or tests compatible with undercover, please add undercover to your gemfile to use the undercover formatter the test helper.
# Gemfile
group :test do
gem 'undercover'
end
# the very top of spec_helper.rb
require 'simplecov'
require 'undercover/simplecov_formatter'
# optional, the filename defaults to `coverage.json` and is automatically recognised by the gem
# SimpleCov::Formatter::Undercover.output_filename = 'my_project_coverage.json'
SimpleCov.formatter = SimpleCov::Formatter::Undercover
SimpleCov.start do
skip(/^\/spec\//) # For RSpec (`add_filter` on SimpleCov < 1.0)
skip(/^\/test\//) # For Minitest
enable_coverage(:branch) # Report branch coverage to trigger branch-level undercover warnings
end
# ...
Then run your test suite once through to generate the initial coverage file before you can run the undercover command.
Undercover supports coverage analysis for ERB view templates. This requires:
enable_coverage(:eval)# spec_helper.rb
SimpleCov.start do
enable_coverage(:branch)
enable_coverage(:eval) # Required for view coverage
end
--include-files since view coverage is not enabled by default:undercover --include-files '*.rb,*.erb'
Or in your .undercover config file:
-f *.rb,*.erb
Why opt-in? View coverage requires enable_coverage(:eval), which is not enabled by default in SimpleCov. Without it, view files would be flagged as having 0% coverage (false positives).
If you're upgrading from an older version of undercover that used LCOV, you can migrate to the new SimpleCov formatter:
gem 'undercover' to your test group# Gemfile
group :test do
gem 'undercover'
end
# spec_helper.rb
require 'simplecov'
require 'undercover/simplecov_formatter'
SimpleCov.formatter = SimpleCov::Formatter::Undercover
--simplecov flag instead of --lcov, or rely on auto-detection of coverage/coverage.jsonNote: LCOV support will be deprecated in a future release, but remains fully functional for existing projects.
Undercover works when the coverage report covers a subdirectory of the repository rather than the whole thing, which is the usual shape of a monorepo:
monorepo/
.git
apps/
dash/ <- SimpleCov runs here
coverage/coverage.json
admin/
Run it from the directory SimpleCov covered and point --git-dir at the repository:
cd apps/dash
undercover --git-dir ../../.git --compare origin/main
Undercover works out that coverage was recorded under apps/dash by matching the paths in the
report against the files in the repository, so there is nothing to configure. In particular:
SimpleCov.start('rails') skips db/, config/ and
vendor/, and those files will not be reported even though git sees them as apps/dash/db/...apps/admin, are left alone. They are outside the
coverage report and there is nothing to judge them byapps/dash/lib/foo.rb), so they line
up with what CI annotations expectFile globs passed to --include-files and --exclude-files are matched relative to the covered
directory, so write lib/**/*.rb rather than apps/dash/lib/**/*.rb.
Invoked with no arguments, Undercover will flag all untested methods and classes from the current diff:
undercover
Use the -c --compare ref flag to specify a git ref (commit hash, branch name, tag) to compare against. This is a recommended usage for CI/CD build environments, as undercover will exit 1 if there are any warnings.
undercover --compare origin/master
Check out docs/ for CI configuration examples:
Merging coverage results (sample gist) is required for parallel tests before processing with undercover.
A few options exist to provide automated comments from undercover in Pull Request reviews, which is the most streamlined way to add Undercover to your development workflow.
undercover Pull Request feedback delivered natively with GitHub ChecksOptions can be passed when running the command from the command line:
Usage: undercover [options]
-s, --simplecov path SimpleCov JSON report file
-l, --lcov path LCOV report file path (to be deprecated)
-g, --git-dir dir Override `.git` with a custom directory
-c, --compare ref Generate coverage warnings for all changes after `ref`
--commits-only Only check committed changes after `--compare ref`, ignoring the index and working tree
-r, --ruby-syntax ver Ruby syntax version, one of: current, ruby18, ruby19, ruby20, ruby21, ruby22, ruby23, ruby24, ruby25, ruby26, ruby30, ruby31, ruby32, ruby33
-w, --max-warnings limit Maximum number of warnings to generate before stopping analysis. Useful as a performance improvement for large diffs.
-f, --include-files globs Include files matching specified glob patterns (comma separated). Defaults to '*.rb,*.rake,*.ru,Rakefile'
-x, --exclude-files globs Skip files matching specified glob patterns (comma separated). Empty by default.
--format FORMAT Output format: text, json (default: text)
-h, --help Prints this help
--version Show version
Use --format json to get machine-readable output, useful for CI integrations and tooling:
undercover --compare origin/main --format json
Output structure:
{
"warnings": [
{
"node": "Foo#bar",
"type": "instance method",
"file": "lib/foo.rb",
"first_line": 10,
"last_line": 20,
"coverage": 0.5,
"uncovered_lines": [11],
"uncovered_branches": [
{
"line": 13,
"block": 0,
"branch": 0,
"description": "if user.admin?"
},
{
"line": 16,
"block": 0,
"branch": 1,
"description": "else"
},
{
"line": 19,
"block": 0,
"branch": 2,
"description": "? role == :admin → else"
}
]
}
],
"summary": {
"total_warnings": 1,
"files_affected": 1
}
}
Each warning includes:
uncovered_lines — line numbers with zero execution countuncovered_branches — branch entries that were never taken, each with line, block, and branch identifiers. The optional description field names the branch from the source (e.g. "if user.admin?", "else", "when :active"). For same-line branches such as ternaries, the arm type is appended after → (e.g. "? role == :admin → else").Branch-level entries require enable_coverage(:branch) in your SimpleCov configuration (see Setting up coverage reporting).
A configuration file named .undercover can be created at the top level of a project's directory containing the same set of options for the CLI.
Example file:
-l path/to/different.lcov
-c origin/master
The options set in the file can be overriden by passing arguments when invoking the executable.
Options assume that the program is run from the top level of the project directory.
Projects with low or nonexistent test coverage are likely to generate large numbers of warnings. While the default workflow would be to address them before the PR approval, your strategy might be different.
In order to acknowledge an untested change and remove the UndercoverCI warning with the intention to improve later (or never), you can wrap the code block with SimpleCov's skip markers, e.g.
# simplecov:disable
def skip_this_method
never_reached
end
# simplecov:enable
The legacy # :nocov: token still works (deprecated in SimpleCov 1.0). Read more in SimpleCov's readme.
I wanted to create a tool to help others and myself ensure that tests are written for all the recent code changes. This should be useful for any ruby project, but especially those large or legacy codebases that lack testing (and we can't or don't want to invest in full test coverage).
The goal was to provide automated warnings, that are:
For more background, please read the blog post.
After checking out the repo, run bundle to install dependencies. Then, run rake to run the tests and RuboCop. You can also run pry -r 'undercover' for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.
Bug reports and pull requests are welcome on GitHub at https://github.com/grodowski/undercover.
The gem is available as open source under the terms of the MIT License.
1,434 followers · starred Jul 2018
1,338 followers · starred Jul 2018
907 followers · starred Jan 2020
1,421 followers · starred Dec 2019
undercover warns about methods, classes and blocks that were changed without tests, to help you easily find untested code and reduce the number of bugs. It does so by analysing data from git diffs, code structure and SimpleCov coverage reports
See the codeundercover warns about methods, classes and blocks that were changed without tests, to help you easily find untested code and reduce the number of bugs. It does so by analysing data from git diffs, code structure and SimpleCov coverage reports.
Works with any Ruby CI pipeline as well as locally as a CLI.
A sample output of undercover ran before a commit may look like this:

And like this, given that specs were added:

Add this line to your application's Gemfile:
gem 'undercover'
And then execute:
$ bundle
Or install it yourself as:
$ gem install undercover
[!NOTE]
undercoverrequires SimpleCov >= 1.0 (and Ruby >= 3.2). For older SimpleCov (0.x) or Ruby setups, pinundercoverto~> 0.8.0.
To make your specs or tests compatible with undercover, please add undercover to your gemfile to use the undercover formatter the test helper.
# Gemfile
group :test do
gem 'undercover'
end
# the very top of spec_helper.rb
require 'simplecov'
require 'undercover/simplecov_formatter'
# optional, the filename defaults to `coverage.json` and is automatically recognised by the gem
# SimpleCov::Formatter::Undercover.output_filename = 'my_project_coverage.json'
SimpleCov.formatter = SimpleCov::Formatter::Undercover
SimpleCov.start do
skip(/^\/spec\//) # For RSpec (`add_filter` on SimpleCov < 1.0)
skip(/^\/test\//) # For Minitest
enable_coverage(:branch) # Report branch coverage to trigger branch-level undercover warnings
end
# ...
Then run your test suite once through to generate the initial coverage file before you can run the undercover command.
Undercover supports coverage analysis for ERB view templates. This requires:
enable_coverage(:eval)# spec_helper.rb
SimpleCov.start do
enable_coverage(:branch)
enable_coverage(:eval) # Required for view coverage
end
--include-files since view coverage is not enabled by default:undercover --include-files '*.rb,*.erb'
Or in your .undercover config file:
-f *.rb,*.erb
Why opt-in? View coverage requires enable_coverage(:eval), which is not enabled by default in SimpleCov. Without it, view files would be flagged as having 0% coverage (false positives).
If you're upgrading from an older version of undercover that used LCOV, you can migrate to the new SimpleCov formatter:
gem 'undercover' to your test group# Gemfile
group :test do
gem 'undercover'
end
# spec_helper.rb
require 'simplecov'
require 'undercover/simplecov_formatter'
SimpleCov.formatter = SimpleCov::Formatter::Undercover
--simplecov flag instead of --lcov, or rely on auto-detection of coverage/coverage.jsonNote: LCOV support will be deprecated in a future release, but remains fully functional for existing projects.
Undercover works when the coverage report covers a subdirectory of the repository rather than the whole thing, which is the usual shape of a monorepo:
monorepo/
.git
apps/
dash/ <- SimpleCov runs here
coverage/coverage.json
admin/
Run it from the directory SimpleCov covered and point --git-dir at the repository:
cd apps/dash
undercover --git-dir ../../.git --compare origin/main
Undercover works out that coverage was recorded under apps/dash by matching the paths in the
report against the files in the repository, so there is nothing to configure. In particular:
SimpleCov.start('rails') skips db/, config/ and
vendor/, and those files will not be reported even though git sees them as apps/dash/db/...apps/admin, are left alone. They are outside the
coverage report and there is nothing to judge them byapps/dash/lib/foo.rb), so they line
up with what CI annotations expectFile globs passed to --include-files and --exclude-files are matched relative to the covered
directory, so write lib/**/*.rb rather than apps/dash/lib/**/*.rb.
Invoked with no arguments, Undercover will flag all untested methods and classes from the current diff:
undercover
Use the -c --compare ref flag to specify a git ref (commit hash, branch name, tag) to compare against. This is a recommended usage for CI/CD build environments, as undercover will exit 1 if there are any warnings.
undercover --compare origin/master
Check out docs/ for CI configuration examples:
Merging coverage results (sample gist) is required for parallel tests before processing with undercover.
A few options exist to provide automated comments from undercover in Pull Request reviews, which is the most streamlined way to add Undercover to your development workflow.
undercover Pull Request feedback delivered natively with GitHub ChecksOptions can be passed when running the command from the command line:
Usage: undercover [options]
-s, --simplecov path SimpleCov JSON report file
-l, --lcov path LCOV report file path (to be deprecated)
-g, --git-dir dir Override `.git` with a custom directory
-c, --compare ref Generate coverage warnings for all changes after `ref`
--commits-only Only check committed changes after `--compare ref`, ignoring the index and working tree
-r, --ruby-syntax ver Ruby syntax version, one of: current, ruby18, ruby19, ruby20, ruby21, ruby22, ruby23, ruby24, ruby25, ruby26, ruby30, ruby31, ruby32, ruby33
-w, --max-warnings limit Maximum number of warnings to generate before stopping analysis. Useful as a performance improvement for large diffs.
-f, --include-files globs Include files matching specified glob patterns (comma separated). Defaults to '*.rb,*.rake,*.ru,Rakefile'
-x, --exclude-files globs Skip files matching specified glob patterns (comma separated). Empty by default.
--format FORMAT Output format: text, json (default: text)
-h, --help Prints this help
--version Show version
Use --format json to get machine-readable output, useful for CI integrations and tooling:
undercover --compare origin/main --format json
Output structure:
{
"warnings": [
{
"node": "Foo#bar",
"type": "instance method",
"file": "lib/foo.rb",
"first_line": 10,
"last_line": 20,
"coverage": 0.5,
"uncovered_lines": [11],
"uncovered_branches": [
{
"line": 13,
"block": 0,
"branch": 0,
"description": "if user.admin?"
},
{
"line": 16,
"block": 0,
"branch": 1,
"description": "else"
},
{
"line": 19,
"block": 0,
"branch": 2,
"description": "? role == :admin → else"
}
]
}
],
"summary": {
"total_warnings": 1,
"files_affected": 1
}
}
Each warning includes:
uncovered_lines — line numbers with zero execution countuncovered_branches — branch entries that were never taken, each with line, block, and branch identifiers. The optional description field names the branch from the source (e.g. "if user.admin?", "else", "when :active"). For same-line branches such as ternaries, the arm type is appended after → (e.g. "? role == :admin → else").Branch-level entries require enable_coverage(:branch) in your SimpleCov configuration (see Setting up coverage reporting).
A configuration file named .undercover can be created at the top level of a project's directory containing the same set of options for the CLI.
Example file:
-l path/to/different.lcov
-c origin/master
The options set in the file can be overriden by passing arguments when invoking the executable.
Options assume that the program is run from the top level of the project directory.
Projects with low or nonexistent test coverage are likely to generate large numbers of warnings. While the default workflow would be to address them before the PR approval, your strategy might be different.
In order to acknowledge an untested change and remove the UndercoverCI warning with the intention to improve later (or never), you can wrap the code block with SimpleCov's skip markers, e.g.
# simplecov:disable
def skip_this_method
never_reached
end
# simplecov:enable
The legacy # :nocov: token still works (deprecated in SimpleCov 1.0). Read more in SimpleCov's readme.
I wanted to create a tool to help others and myself ensure that tests are written for all the recent code changes. This should be useful for any ruby project, but especially those large or legacy codebases that lack testing (and we can't or don't want to invest in full test coverage).
The goal was to provide automated warnings, that are:
For more background, please read the blog post.
After checking out the repo, run bundle to install dependencies. Then, run rake to run the tests and RuboCop. You can also run pry -r 'undercover' for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.
Bug reports and pull requests are welcome on GitHub at https://github.com/grodowski/undercover.
The gem is available as open source under the terms of the MIT License.
1,434 followers · starred Jul 2018
1,338 followers · starred Jul 2018
907 followers · starred Jan 2020
1,421 followers · starred Dec 2019