rspec-covers adds coverage intent metadata to RSpec examples.
It lets a spec declare the production code it is responsible for with covers,
declare allowed dependencies with uses, and optionally fail examples that
execute production code outside that declaration.
Add the gem to your application's Gemfile:
gem "rspec-covers"Configure the integration from spec_helper.rb:
require "rspec/covers"
RSpec.configure do |config|
RSpec::Covers.configure(config) do |covers|
covers.strict = true
covers.risky = :fail
covers.mode = :executed
covers.undeclared = :warn
covers.production_paths = %w[app lib]
covers.allowlist = %w[app/models/application_record.rb lib/instrumentation/**]
covers.suggest = true
covers.validate_declarations = true
covers.report_path = ".rspec-covers/result.json"
end
endDeclare intent on example groups or examples:
RSpec.describe UserCreator, covers: UserCreator do
it "creates a user", covers: "UserCreator#call" do
expect(UserCreator.new(params).call).to be_persisted
end
it "notifies", covers: "UserCreator#call", uses: [Notifier, "User.create!"] do
expect(UserCreator.new(params).call).to be_success
end
endcovers and uses accept:
- constants such as
UserCreator - method strings such as
"UserCreator#call"or"User.create!" - file paths or globs such as
"app/services/**/*.rb" - regular expressions matched against production method labels and paths
For each example, rspec-covers records per-example line coverage with
Coverage.peek_result.
When strict is enabled, an example is risky if it executes production lines
that are not covered by its covers or uses declaration. Risky examples raise
RSpec::Covers::StrictCoverageError by default.
Set covers.risky = :report to keep the RSpec example green while still
recording the example as risky in formatter output and JSON reports.
Examples with no declaration follow covers.undeclared:
:ignoreignores them:warnprints a warning:riskyfails when production code is executed
With covers.suggest = true, undeclared examples receive ranked covers
suggestions. The scorer combines:
NC: spec file or described class naming conventionNCC: token overlap between example text and method labelsLCBA: the production call immediately beforeexpect(...)dynamic: suite-wide tf-idf over production calls
Run:
rake covers:suggest[.rspec-covers/result.json]With covers.validate_declarations = true, examples that already declare
covers are also scored. Weakly supported declarations are written to
declaration_validation in the JSON report and shown by the formatter.
covers.mode = :checked narrows executed coverage to production methods that
contributed to expectations. It tracks production method return values passed to
expect(...) and the last production call before each expectation. This is an
approximation of checked coverage, not a full dynamic slice.
In checked mode, the JSON report includes unchecked_locations: production
lines that ran during the example but were not attributed to expectation
inputs.
rake covers:report[.rspec-covers/result.json]
rake covers:suggest[.rspec-covers/result.json]
rake covers:evaluate[ground_truth.json,predictions.json]The JSON report also includes orphan_methods: production methods found from
loaded constants and static Ripper scans of production_paths that were not
named by any example's covers declaration.
covers:evaluate reports traceability precision, recall, F1, and
applicability for suggestions against a JSON ground-truth file. If the ground
truth examples include risky, seeded, or checked_locations, it also
reports strict coverage, seeded strict recall, and checked coverage metrics.
Targets can be declared in the ground truth under targets and are reported as
pass/fail comparisons.
Ground truth can be a simple mapping:
{
"examples": {
"spec/example_spec.rb[1:1]": ["UserCreator#call"]
}
}Or an array with strict and checked labels:
{
"targets": {
"traceability.f1": 0.75,
"strict.recall": 0.90,
"checked.f1": 0.70
},
"examples": [
{
"id": "spec/example_spec.rb[1:1]",
"covers": ["UserCreator#call"],
"risky": true,
"seeded": true,
"checked_locations": [{ "file": "app/user_creator.rb", "line": 12 }]
}
]
}Enable JSON formatter events with:
covers.json_events = trueWhen RSpec::Covers::Formatter is active, it emits JSON Lines with
type: "rspec_covers.example" for each recorded example.
The core coverage engine can also be attached to Minitest:
require "minitest/covers"
Minitest::Covers.configure do |covers|
covers.strict = true
end
class UserCreatorTest < Minitest::Test
covers "UserCreator#call"
uses "Notifier.deliver", target: :test_creates_user
def test_creates_user
assert UserCreator.new(params).call
end
endClass-level covers / uses apply to all tests in the class. Pass
target: :test_name for method-level Minitest metadata.
In checked mode, Minitest assert(value) records value as the expectation
actual.
Line ranges use RubyVM::AbstractSyntaxTree on CRuby and fall back to
method_source when the VM does not expose AST ranges. Coverage from threads
created inside an example belongs to the same process and may appear in that
example's evidence.
If SimpleCov or another tool has already started Ruby coverage, rspec-covers
uses Coverage.peek_result deltas and does not restart coverage.