Skip to main content

Stryker Mutation Testing: Measure Real Test Quality

Code coverage tells you which lines were run. Mutation testing tells you whether your tests actually verify the code's behaviour. Stryker introduces hundreds of tiny bugs into your codebase one at a time and checks if your tests catch them. If a test suite scores 95% coverage but only 40% mutation score, the tests are not doing their job.

✓ Reveals gaps code coverage hides ✓ Works with Jest, Vitest, Mocha, Jasmine ✓ HTML report with killed/survived breakdown ✓ Configurable thresholds for CI gates

What Is Mutation Testing?

Mutation testing is a technique where automated tooling makes small, deliberate changes (mutations) to your source code and checks whether your tests detect each change. If the test suite still passes after a mutation, the mutant "survived" — meaning your tests have a blind spot at that exact point in the code.

The mutation score is: (killed mutants / total mutants) × 100. A score of 75% means 25% of the artificial bugs slipped past your tests undetected.

How Stryker Works

Mutant Status Meaning Action
Killed A test failed — bug was caught Good, no action needed
Survived All tests passed — bug escaped Write a test that catches this
No Coverage No test even ran this code Add coverage first
Timeout Mutant caused an infinite loop Counts as killed

Installation and stryker.config.mjs

npm install --save-dev @stryker-mutator/core @stryker-mutator/jest-runner
# or for Vitest:
npm install --save-dev @stryker-mutator/core @stryker-mutator/vitest-runner
// stryker.config.mjs
export default {
  testRunner: 'jest',           // or 'vitest'
  coverageAnalysis: 'perTest',  // fastest: only re-run relevant tests
  mutate: ['src/**/*.ts', '!src/**/*.spec.ts'],
  reporters: ['html', 'clear-text', 'progress'],
  thresholds: { high: 80, low: 60, break: 50 },
  disableTypeChecks: true,      // speeds up runs significantly
};

Running Stryker

# Run mutation tests:
npx stryker run

# Run only on changed files (faster in development):
npx stryker run --mutate "src/auth/**/*.ts"

# Output: reports/mutation/mutation.html — open in browser for full report

Common Surviving Mutant Types

Mutant Type Example Change What to Add
Boundary condition age > 18 → age >= 18 Test with value 18
Boolean flip isActive && isValid → isActive || isValid Test where one condition is false
Return value return result → return undefined Assert the return value explicitly
String literal 'error' → '' Assert error message content

Mutation Score Thresholds

The thresholds config sets three values. high (default 80): scores at or above are shown in green. low (default 60): scores below are shown in red. break (default null): if set, Stryker exits with a non-zero code below this threshold, blocking CI. Set break: 50 as a minimum quality gate, and aim to push toward 70-80% over time as you improve your test suite.

Share this tool: X / Twitter LinkedIn
✓ 60,000+ developers use DevPlaybook ★★★★★ 4.8 / 5 on Gumroad ✓ MIT licensed · Instant download ✓ No login required

Related Products

on Gumroad

You Might Also Need

More Free Tools

Enjoying this tool? Get more like it.

Weekly developer tool guides, productivity playbooks & AI tricks. Free. No spam.

DevPlaybook Pro 7 days free

Unlock AI-Powered Dev Tools

  • ⚡ AI Code Review, Doc Generator & SQL Builder
  • ⚡ All premium templates & early access
  • ⚡ Member discounts on Gumroad products
Start Free Trial →

$9/mo after trial · Cancel anytime

Want the full toolkit?

Get DevPlaybook Pro

Every template, guide, boilerplate, and automation script in one bundle. 13 premium products — grab them all at once and save big.

$79
$241 separately Save $162 (67% off)
Get DevPlaybook Pro — $79 →

MIT licensed · Instant download · No subscription

See what's included → Browse all deals →

Frequently Asked Questions

What is the difference between code coverage and mutation score?▼

Code coverage tells you which lines of code were executed during tests. It says nothing about whether your assertions actually verify the behaviour of that code. Mutation score measures how many of Stryker's artificial bugs (mutants) your tests caught and killed. A test that covers a line but has no assertions will show 100% coverage but 0% mutation score. Mutation score is a much stronger indicator of test quality.

What is a mutant in Stryker?▼

A mutant is a small, targeted change Stryker makes to your source code — one at a time — to see if your tests detect it. Examples: changing && to ||, flipping > to >=, replacing a return value with undefined, or deleting a conditional block. Stryker runs the full test suite against each mutant. A 'killed' mutant means at least one test failed, proving those tests guard that behaviour. A 'survived' mutant means no test caught the change — a gap in your suite.

How do I configure Stryker with Vitest?▼

Install the Vitest runner: npm install --save-dev @stryker-mutator/core @stryker-mutator/vitest-runner. In stryker.config.mjs, set testRunner: 'vitest' and coverageAnalysis: 'perTest'. For large projects, add disableTypeChecks: true to significantly speed up runs. Stryker will use your existing vitest.config.ts automatically.

What mutation score threshold should I aim for?▼

For business-critical code (payment logic, auth, data validation), aim for 80%+. For general application code, 60-70% is a realistic goal. Scores below 40% typically indicate tests that were written to satisfy coverage requirements rather than to verify behaviour. Do not chase 100% — some mutants are inherently equivalent (the mutation produces the same behaviour) and are not worth the test complexity.

How do I ignore specific mutants in Stryker?▼

Use the mutate config to exclude files: mutate: ['src/**/*.ts', '!src/**/*.spec.ts']. To ignore specific mutant types project-wide, use the excludedMutations option: ['StringLiteral', 'BlockStatement']. For fine-grained ignoring, add a Stryker comment in source code: // Stryker disable next-line ArithmeticOperator. Use ignoring sparingly — it should document why a mutant is intentionally untested, not cover up gaps.

How does Stryker help find untested edge cases?▼

Stryker systematically mutates boundary conditions (changing > to >= or <), boolean operators (&&, ||), null checks, and return values. Surviving mutants in boundary condition code reveal tests that pass with one value but would fail with the edge case one step away. This is exactly where off-by-one bugs and null pointer errors hide. Running Stryker on a function like validateAge(age) will quickly reveal whether your tests check the boundary (18, 17, 19) or only the happy path.