Mutation testing patterns for verifying test effectiveness. Use when analyzing branch code to find weak or missing tests...
Mutation testing answers: "Would my tests catch this bug?" by actually introducing bugs and running tests.
CRITICAL: This skill actually mutates code and runs tests. Follow this exact process:
# Get changed files on the branch
git diff main...HEAD --name-only | grep -E '\.(ts|js|tsx|jsx|vue)$' | grep -v '\.test\.' | grep -v '\.spec\.'
Execute this loop for each mutation:
1. READ the original file and note exact content
2. APPLY one mutation (edit the code)
3. RUN tests: pnpm test --run (or specific test file)
4. RECORD result: KILLED (test failed) or SURVIVED (test passed)
5. RESTORE original code immediately
6. Repeat for next mutation
After all mutations, provide a summary table:
| Mutation | Location | Result | Action Needed |
|----------|----------|--------|---------------|
| `>` → `>=` | file.ts:42 | SURVIVED | Add boundary test |
| `&&` → `||` | file.ts:58 | KILLED | None |
| Original | Mutate To | Why It Matters |
|---|---|---|
< |
<= |
Boundary not tested |
> |
>= |
Boundary not tested |
<= |
< |
Equality case missed |
>= |
> |
Equality case missed |
| Original | Mutate To | Why It Matters |
|---|---|---|
&& |
|| |
Only tested when both true |
|| |
&& |
Only tested when both false |
!condition |
condition |
Negation not verified |
| Original | Mutate To | Why It Matters |
|---|---|---|
+ |
- |
Tested with 0 only |
- |
+ |
Tested with 0 only |
* |
/ |
Tested with 1 only |
| Original | Mutate To | Why It Matters |
|---|---|---|
return x |
return null |
Return value not asserted |
return true |
return false |
Boolean return not checked |
if (cond) return |
// removed |
Early exit not tested |
| Original | Mutate To | Why It Matters |
|---|---|---|
array.push(x) |
// removed |
Side effect not verified |
await save(x) |
// removed |
Async operation not verified |
emit('event') |
// removed |
Event emission not tested |
Original code (src/utils/validation.ts:15):
export function isValidAge(age: number): boolean {
return age >= 18 && age <= 120;
}
Mutation 1: Change >= to >
export function isValidAge(age: number): boolean {
return age > 18 && age <= 120; // MUTATED
}
Run tests:
pnpm test --run src/__tests__/validation.test.ts
Result: Tests PASS → SURVIVED (Bad! Need test for isValidAge(18))
Restore original code immediately
Mutation 2: Change && to ||
export function isValidAge(age: number): boolean {
return age >= 18 || age <= 120; // MUTATED
}
Run tests:
pnpm test --run src/__tests__/validation.test.ts
Result: Tests FAIL → KILLED (Good! Tests catch this bug)
Restore original code immediately
| State | Meaning | Action |
|---|---|---|
| KILLED | Test failed with mutant | Tests are effective |
| SURVIVED | Tests passed with mutant | Add or strengthen test |
| TIMEOUT | Tests hung (infinite loop) | Counts as detected |
Score = (Killed + Timeout) / Total Mutations * 100
| Score | Quality |
|---|---|
| < 60% | Weak - significant test gaps |
| 60-80% | Moderate - improvements needed |
| 80-90% | Good - minor gaps |
| > 90% | Strong test suite |
When a mutant survives, add a test that would catch it:
>= → >)// Add boundary test
it('accepts exactly 18 years old', () => {
expect(isValidAge(18)).toBe(true); // Would fail if >= became >
});
&& → ||)// Add test with mixed conditions
it('rejects when only one condition met', () => {
expect(isValidAge(15)).toBe(false); // Would pass if && became ||
});
// Add side effect verification
it('saves to database', async () => {
await processOrder(order);
expect(db.save).toHaveBeenCalledWith(order); // Would fail if save removed
});
For each mutation, ask:
// WEAK: Only tests success case
it('validates', () => {
expect(validate(goodInput)).toBe(true);
});
// STRONG: Tests both cases
it('validates good input', () => {
expect(validate(goodInput)).toBe(true);
});
it('rejects bad input', () => {
expect(validate(badInput)).toBe(false);
});
// WEAK: Mutation survives
expect(multiply(5, 1)).toBe(5); // 5*1 = 5/1 = 5
// STRONG: Mutation detected
expect(multiply(5, 3)).toBe(15); // 5*3 ≠ 5/3
// WEAK: No return value check
it('processes', () => {
process(data); // No assertion!
});
// STRONG: Asserts outcome
it('processes', () => {
const result = process(data);
expect(result).toEqual(expected);
});
After completing mutation testing, provide:
## Mutation Testing Results
**Target**: `src/features/workout/utils.ts` (functions: X, Y, Z)
**Total Mutations**: 12
**Killed**: 9
**Survived**: 3
**Score**: 75%
### Surviving Mutants (Action Required)
| # | Location | Original | Mutated | Suggested Test |
|---|----------|----------|---------|----------------|
| 1 | line 42 | `>=` | `>` | Test boundary value |
| 2 | line 58 | `&&` | `\|\|` | Test mixed conditions |
| 3 | line 71 | `emit()` | removed | Verify event emission |
### Killed Mutants (Tests Effective)
- Line 35: `+` → `-` killed by `calculation.test.ts`
- Line 48: `true` → `false` killed by `validate.test.ts`
- ...
systematic-debugging - Root cause analysistesting-conventions - Query priority, expect.poll()vue-integration-testing - Page objects, browser modevitest-mocking - Test doubles and mocking patterns