| name | mutation-testing |
| description | Validate test effectiveness with mutation testing using Stryker (TypeScript/JavaScript with Vitest or bun test via @hughescr/stryker-bun-runner) and mutmut (Python). Find weak tests that pass despite code mutations. Use to improve test quality. |
| allowed-tools | Bash, Read, Edit, Write, Grep, Glob, TodoWrite |
| license | MIT |
Mutation Testing
Expert knowledge for mutation testing - validating that your tests actually catch bugs by introducing deliberate code mutations.
Core Concept
- Mutants: Small code changes introduced automatically
- Killed: Test fails with mutation (good - test caught the bug)
- Survived: Test passes with mutation (bad - weak test)
- Score: Percentage of mutants killed (aim for 80%+)
TypeScript/JavaScript (Stryker)
Vitest Runner
Installation
bun add -d @stryker-mutator/core @stryker-mutator/vitest-runner
npm install -D @stryker-mutator/core @stryker-mutator/vitest-runner
Configuration
export default {
packageManager: 'bun',
reporters: ['html', 'clear-text', 'progress'],
testRunner: 'vitest',
coverageAnalysis: 'perTest',
mutate: ['src/**/*.ts', '!src/**/*.test.ts'],
thresholds: { high: 80, low: 60, break: 60 },
incremental: true,
}
Bun Native Runner (bun test)
Use when projects use bun test directly (not Vitest).
Requirements
- Bun >= 1.3.7 (needs TestReporter WebSocket events from Bun PR #25986)
@stryker-mutator/core ^9.6.1
Installation
bun add -D @hughescr/stryker-bun-runner @stryker-mutator/core
Configuration
export default {
testRunner: 'bun',
coverageAnalysis: 'perTest',
mutate: ['src/**/*.ts', '!src/**/*.test.ts'],
thresholds: { high: 80, low: 60, break: 60 },
incremental: true,
bun: {
inspectorTimeout: 5000,
},
}
Key Behaviors
- Sequential execution: Runs tests with
--concurrency=1 for accurate per-test coverage. Slower than parallel but required for correct test-to-mutant correlation.
- Concurrent test patching: Automatically patches
describe.concurrent(), test.concurrent(), it.concurrent() to run sequentially during mutation testing — no code changes needed.
- Inspector Protocol: Uses Bun's WebSocket Inspector API to discover tests and correlate coverage.
Running Stryker
bunx stryker run
bunx stryker run --incremental
bunx stryker run --mutate "src/utils/**/*.ts"
open reports/mutation/html/index.html
Example: Weak Test
function calculateDiscount(price: number, percentage: number): number {
return price - (price * percentage / 100)
}
test('applies discount', () => {
expect(calculateDiscount(100, 10)).toBeDefined()
})
test('applies discount correctly', () => {
expect(calculateDiscount(100, 10)).toBe(90)
expect(calculateDiscount(100, 20)).toBe(80)
expect(calculateDiscount(50, 10)).toBe(45)
})
Python (mutmut)
Installation
uv add --dev mutmut
Running mutmut
uv run mutmut run
uv run mutmut results
uv run mutmut show 1
uv run mutmut html
open html/index.html
Common Mutation Types
Mutation Score Targets
| Score | Quality | Action |
|---|
| 90%+ | Excellent | Maintain quality |
| 80-89% | Good | Small improvements |
| 70-79% | Acceptable | Focus on weak areas |
| < 60% | Poor | Major improvements needed |
Improving Weak Tests
Pattern: Insufficient Assertions
test('calculates sum', () => {
expect(sum([1, 2, 3])).toBeGreaterThan(0)
})
test('calculates sum correctly', () => {
expect(sum([1, 2, 3])).toBe(6)
expect(sum([0, 0, 0])).toBe(0)
expect(sum([])).toBe(0)
})
Pattern: Boundary Conditions
test('validates age boundaries', () => {
expect(isValidAge(18)).toBe(true)
expect(isValidAge(17)).toBe(false)
expect(isValidAge(100)).toBe(true)
expect(isValidAge(101)).toBe(false)
})
Best Practices
- Start with core business logic modules
- Ensure 80%+ coverage before mutation testing
- Run incrementally (only changed files)
- Focus on important files first
- Don't expect 100% mutation score (equivalent mutants exist)
Workflow
bun test --coverage
bunx stryker run
open reports/mutation/html/index.html
bunx stryker run --incremental
See Also
vitest-testing - Unit testing framework
test-quality-analysis - Detecting test smells