Claude-skills mutation-testing
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.
install
source · Clone the upstream repo
git clone https://github.com/secondsky/claude-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/secondsky/claude-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/mutation-testing/skills/mutation-testing" ~/.claude/skills/secondsky-claude-skills-mutation-testing && rm -rf "$T"
manifest:
plugins/mutation-testing/skills/mutation-testing/SKILL.mdsource content
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
# Using Bun bun add -d @stryker-mutator/core @stryker-mutator/vitest-runner # Using npm npm install -D @stryker-mutator/core @stryker-mutator/vitest-runner
Configuration
// stryker.config.mjs 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
)
bun testUse 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.0.0
Installation
bun add -D @hughescr/stryker-bun-runner @stryker-mutator/core
Configuration
// stryker.conf.mjs export default { testRunner: 'bun', coverageAnalysis: 'perTest', mutate: ['src/**/*.ts', '!src/**/*.test.ts'], thresholds: { high: 80, low: 60, break: 60 }, incremental: true, bun: { inspectorTimeout: 5000, // WebSocket Inspector connection timeout (ms) // bunPath: '/path/to/bun', // only if custom Bun install // timeout: 30000, // test timeout (ms) // env: { DEBUG: 'true' }, // bunArgs: ['--bail'], }, }
Key Behaviors
- Sequential execution: Runs tests with
for accurate per-test coverage. Slower than parallel but required for correct test-to-mutant correlation.--concurrency=1 - Concurrent test patching: Automatically patches
,describe.concurrent()
,test.concurrent()
to run sequentially during mutation testing — no code changes needed.it.concurrent() - Inspector Protocol: Uses Bun's WebSocket Inspector API to discover tests and correlate coverage.
Running Stryker
# Run mutation testing bunx stryker run # Incremental mode (only changed files) bunx stryker run --incremental # Specific files bunx stryker run --mutate "src/utils/**/*.ts" # Open HTML report open reports/mutation/html/index.html
Example: Weak Test
// Source code function calculateDiscount(price: number, percentage: number): number { return price - (price * percentage / 100) } // ❌ WEAK: Test passes even if we mutate calculation test('applies discount', () => { expect(calculateDiscount(100, 10)).toBeDefined() // Too weak! }) // ✅ STRONG: Test catches mutation 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
# Run mutation testing uv run mutmut run # Show results uv run mutmut results # Show specific mutant uv run mutmut show 1 # Generate HTML report uv run mutmut html open html/index.html
Common Mutation Types
// Arithmetic Operator // Original: a + b → a - b, a * b, a / b // Relational Operator // Original: a > b → a >= b, a < b, a <= b // Logical Operator // Original: a && b → a || b // Boolean Literal // Original: true → false
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
// Before: Mutation survives test('calculates sum', () => { expect(sum([1, 2, 3])).toBeGreaterThan(0) // Weak! }) // After: Mutation killed 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
// After: Tests boundaries test('validates age boundaries', () => { expect(isValidAge(18)).toBe(true) // Min valid expect(isValidAge(17)).toBe(false) // Just below expect(isValidAge(100)).toBe(true) // Max valid expect(isValidAge(101)).toBe(false) // Just above })
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
# 1. Ensure good coverage first bun test --coverage # Target: 80%+ coverage # 2. Run mutation testing bunx stryker run # 3. Check report open reports/mutation/html/index.html # 4. Fix survived mutants # 5. Re-run incrementally bunx stryker run --incremental # or: npx stryker run --incremental
See Also
- Unit testing frameworkvitest-testing
- Detecting test smellstest-quality-analysis