Mutineer
A clean-room mutation-testing tool for Ruby. Mutineer mutates your source one change at a time, runs your Minitest suite against each mutant, and reports the ones your tests failed to catch — the gaps where your suite isn't actually testing anything.
- Prism + stdlib only — zero runtime dependencies (Ruby ≥ 3.4).
- One mutation per mutant, validity-checked by re-parsing.
- Fork-isolated, parallel execution (Linux + macOS).
- Coverage-guided — each mutant runs only the test files that cover its line.
📖 mutineer.github.io → — overview, operators, and usage.
Install
gem install mutineer
Or in a Gemfile:
gem "mutineer", group: :test
Usage
mutineer run <source...> --test <test...> [options]
Mutate lib/calculator.rb, checking it against its test, and fail CI if the
mutation score drops below 90%:
mutineer run lib/calculator.rb --test test/calculator_test.rb --threshold 90
Options
| Flag | Meaning | |
|---|---|---|
--test FILE |
Test file covering the sources (repeatable) | |
--operators LIST |
Comma-separated operator names (default: the Tier-1 set) | |
--threshold FLOAT |
Exit 1 when the score is below FLOAT (default: 0 = off) | |
--only NAME |
Restrict to one fully-qualified subject, e.g. Calculator#add |
|
--jobs N |
Parallel worker count (default: processor count) | |
--strategy NAME |
Mutation application: reload whole-file (default) or redefine surgical (7a/7b accepted as deprecated aliases) |
|
| `--format human\ | json` | Report format (default: human) |
--output FILE |
Write the report to FILE instead of stdout | |
--dry-run |
List candidate mutations without executing | |
--list-operators |
List available operators (default vs optional) and exit | |
--version, --help |
Print version / usage and exit |
Exit codes
| Code | Meaning |
|---|---|
0 |
Score ≥ threshold (or no threshold set) |
1 |
Survivors below threshold, or a runtime error |
2 |
Usage / invalid-flag error |
Operators
Run mutineer --list-operators to see them. Default (Tier 1): arithmetic,
comparison, boolean_connector, boolean_literal, statement_removal.
Available but off by default (Tier 2, enable via --operators): return_nil,
literal_mutation, condition_negation.
Rails apps
Rails code needs its environment booted before the suite runs, so point Mutineer
at your app with --rails and run it inside the project's bundle:
RAILS_ENV=test bundle exec mutineer run \
app/models/order.rb --test test/models/order_test.rb --rails
--rails boots config/environment once in the parent process (every mutant
then forks and inherits it), defaults --strategy to redefine (surgical — it
avoids reloading files into the app tree), and reconnects ActiveRecord in each
fork so the database connection is fork-safe. Use --boot FILE to boot a
different entry point. Boot mode requires at least one --test file and runs
those tests for every mutant (coverage-guided selection is not yet available in
boot mode).
Add Mutineer to your Gemfile's test group:
gem "mutineer", group: :test, require: false
Configuration
Mutineer reads an optional .mutineer.yml from the project root (nearest one,
walking up). CLI flags override config; config overrides defaults.
Sources are positional CLI arguments and test files come from --test; the
config file accepts these keys: operators, threshold, jobs, only,
require (extra files to load before mutating), and boot/rails.
# .mutineer.yml
operators: [arithmetic, comparison, boolean_connector, boolean_literal, statement_removal]
threshold: 90
jobs: 4
require:
- config/environment
Coverage results are cached in .mutineer/coverage.json (digest-keyed; rebuilt
automatically when sources change). Add .mutineer/ to your .gitignore.
License
MIT — see LICENSE.