MU

mutation-testing

Uses mutation testing to find weak or missing tests that fail to detect bugs.

Install

mkdir -p .claude/skills/mutation-testing-chloebrett && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/19096" && unzip -o skill.zip -d .claude/skills/mutation-testing-chloebrett && rm skill.zip

Installs to .claude/skills/mutation-testing-chloebrett

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Mutation testing patterns for verifying test effectiveness. Use when analyzing branch code to find weak or missing tests.
121 chars✓ has a “when” trigger
Intermediate

Key capabilities

  • Generate small bugs in production code
  • Execute test suite against mutated code
  • Evaluate if tests fail against mutants
  • Identify tests that do not catch changes
  • Produce a summary report of mutation testing

How it works

The skill introduces small, controlled errors into the production code and then runs the existing test suite against these altered versions. It determines test effectiveness by observing whether the tests fail (kill the mutant) or pass (the mutant survives).

Inputs & outputs

You give it
Production code and its test suite
You get back
A report detailing killed and survived mutants, with actions for survived mutants

When to use mutation-testing

  • Verifying test effectiveness
  • Identifying weak test cases
  • Validating refactoring safety

About this skill

Mutation Testing

For writing good tests (factories, behavior-driven patterns), load the testing skill. This skill focuses on verifying test effectiveness.

Mutation testing answers the question: "Are my tests actually catching bugs?"

Code coverage tells you what code your tests execute. Mutation testing tells you if your tests would detect changes to that code. A test suite with 100% coverage can still miss 40% of potential bugs.


Core Concept

The Mutation Testing Process:

  1. Generate mutants: Introduce small bugs (mutations) into production code
  2. Run tests: Execute your test suite against each mutant
  3. Evaluate results: If tests fail, the mutant is "killed" (good). If tests pass, the mutant "survived" (bad - your tests missed the bug)

The Insight: A surviving mutant represents a bug your tests wouldn't catch.


When to Use This Skill

Use mutation testing analysis when:

  • Reviewing code changes on a branch
  • Verifying test effectiveness after TDD
  • Identifying weak tests that appear to have coverage
  • Finding missing edge case tests
  • Validating that refactoring didn't weaken test suite

Integration with TDD:

RED-GREEN-MUTATE-REFACTOR Cycle
┌─────────────────────────────────────────────────┐
│ 1. RED:      Write failing test                 │
│ 2. GREEN:    Minimum code to pass               │
│ 3. MUTATE:   Verify tests catch real bugs  ◄──  │  ← You are here
│ 4. REFACTOR: Improve structure with confidence  │
└─────────────────────────────────────────────────┘

Why MUTATE before REFACTOR: Mutation testing validates test strength before you restructure code. Refactoring with unverified tests means restructuring code whose safety net you haven't checked.


Execution Process

When verifying test effectiveness, actually mutate the code and run the tests. Do not just reason about whether tests would catch mutations — prove it.

Step 1: Identify Changed Code

# Get files changed on the branch
git diff main...HEAD --name-only | grep -E '\.(ts|js|tsx|jsx)$' | grep -v '\.test\.'

# Get detailed diff for analysis
git diff main...HEAD -- src/

Step 2: Apply Mutations and Run Tests

For each changed function/method, work through the mutation operators (see Mutation Operators section below). For each applicable mutation:

  1. Mutate: Change the production code (e.g., flip * to /, negate a condition)
  2. Run: Execute the test suite
  3. Evaluate: Did a test fail?
    • Yes → mutant killed (good). Revert the mutation.
    • No → mutant survived (bad). Revert the mutation, then add or strengthen a test.
  4. Revert: Always restore the original code before the next mutation

Always revert each mutation before applying the next. Never leave mutated code in place.

You do not need to apply every possible mutation to every line. Focus on:

  • Changed code on the branch
  • Operators most likely to have surviving mutants (see Quick Reference)
  • Conditions with boundary values
  • Boolean logic with multiple operands

Step 3: Produce a Report

After working through the mutations, produce a summary:

## Mutation Testing Report

### Killed (tests caught the mutation)
- `calculateTotal`: `*` → `/` — killed by "calculates total for multiple items"
- `isEligible`: `>=` → `>` — killed by "returns true at exact boundary"

### Survived (tests DID NOT catch the mutation)
- `applyDiscount`: `>` → `>=` — no test for boundary value at exactly 100
  → **Action**: Add boundary test for discount threshold

### Summary
- Mutations applied: 8
- Killed: 6
- Survived: 2
- Mutation score: 75%

Step 4: Kill Surviving Mutants

Not every surviving mutant warrants a new test. Some mutations produce equivalent behavior, and some boundary cases are low-risk enough that the test would add noise without meaningful protection.

Fix immediately when:

  • The mutation represents a realistic bug (wrong operator, inverted condition)
  • The surviving mutant is in critical business logic (money, permissions, eligibility)
  • The fix is a simple boundary test or stronger assertion

Ask the human when:

  • You're unsure whether the mutation represents a real risk
  • The test to kill it would be complex or hard to name clearly
  • The mutation is in a code path that's also covered by integration/E2E tests
  • The surviving mutant feels like an equivalent mutant but you're not certain

Present the mutation, explain why the current tests don't catch it, and let the human decide whether it's worth a new test.

When fixing, follow TDD — write the failing test first, verify it fails against the mutated code, then verify it passes against the original code.


Mutation Operators

Arithmetic Operator Mutations

OriginalMutatedTest Should Verify
a + ba - bAddition behavior
a - ba + bSubtraction behavior
a * ba / bMultiplication behavior
a / ba * bDivision behavior
a % ba * bModulo behavior

Example Analysis:

// Production code
const calculateTotal = (price: number, quantity: number): number => {
  return price * quantity;
};

// Mutant: price / quantity
// Question: Would tests fail if * became /?

// ❌ WEAK TEST - Would NOT catch mutant
it('calculates total', () => {
  expect(calculateTotal(10, 1)).toBe(10); // 10 * 1 = 10, 10 / 1 = 10 (SAME!)
});

// ✅ STRONG TEST - Would catch mutant
it('calculates total', () => {
  expect(calculateTotal(10, 3)).toBe(30); // 10 * 3 = 30, 10 / 3 = 3.33 (DIFFERENT!)
});

Conditional Expression Mutations

OriginalMutatedTest Should Verify
a < ba <= bBoundary value at equality
a < ba >= bBoth sides of condition
a <= ba < bBoundary value at equality
a <= ba > bBoth sides of condition
a > ba >= bBoundary value at equality
a > ba <= bBoth sides of condition
a >= ba > bBoundary value at equality
a >= ba < bBoth sides of condition

Example Analysis:

// Production code
const isAdult = (age: number): boolean => {
  return age >= 18;
};

// Mutant: age > 18
// Question: Would tests fail if >= became >?

// ❌ WEAK TEST - Would NOT catch boundary mutant
it('returns true for adults', () => {
  expect(isAdult(25)).toBe(true);  // 25 >= 18 = true, 25 > 18 = true (SAME!)
});

// ✅ STRONG TEST - Would catch boundary mutant
it('returns true for exactly 18', () => {
  expect(isAdult(18)).toBe(true);  // 18 >= 18 = true, 18 > 18 = false (DIFFERENT!)
});

Equality Operator Mutations

OriginalMutatedTest Should Verify
a === ba !== bBoth equal and not equal cases
a !== ba === bBoth equal and not equal cases
a == ba != bBoth equal and not equal cases
a != ba == bBoth equal and not equal cases

Logical Operator Mutations

OriginalMutatedTest Should Verify
a && ba || bCase where one is true, other is false
a || ba && bCase where one is true, other is false
a ?? ba && bNullish coalescing behavior

Example Analysis:

// Production code
const canAccess = (isAdmin: boolean, isOwner: boolean): boolean => {
  return isAdmin || isOwner;
};

// Mutant: isAdmin && isOwner
// Question: Would tests fail if || became &&?

// ❌ WEAK TEST - Would NOT catch mutant
it('returns true when both conditions met', () => {
  expect(canAccess(true, true)).toBe(true);  // true || true = true && true (SAME!)
});

// ✅ STRONG TEST - Would catch mutant
it('returns true when only admin', () => {
  expect(canAccess(true, false)).toBe(true);  // true || false = true, true && false = false (DIFFERENT!)
});

Boolean Literal Mutations

OriginalMutatedTest Should Verify
truefalseBoth true and false outcomes
falsetrueBoth true and false outcomes
!(a)aNegation is necessary

Block Statement Mutations

OriginalMutatedTest Should Verify
{ code }{ }Side effects of the block

Example Analysis:

// Production code
const processOrder = (order: Order): void => {
  validateOrder(order);
  saveOrder(order);
  sendConfirmation(order);
};

// Mutant: Empty function body
// Question: Would tests fail if all statements removed?

// ❌ WEAK TEST - Would NOT catch mutant
it('processes order without error', () => {
  expect(() => processOrder(order)).not.toThrow();  // Empty function also doesn't throw!
});

// ✅ STRONG TEST - Would catch mutant
it('saves order to database', () => {
  processOrder(order);
  expect(mockDatabase.save).toHaveBeenCalledWith(order);
});

String Literal Mutations

OriginalMutatedTest Should Verify
"text"""Non-empty string behavior
"""Stryker was here!"Empty string behavior

Array Declaration Mutations

OriginalMutatedTest Should Verify
[1, 2, 3][]Non-empty array behavior
new Array(1, 2)new Array()Array contents matter

Unary Operator Mutations

OriginalMutatedTest Should Verify
+a-aSign matters
-a+aSign matters
++a--aIncrement vs decrement
a++a--Increment vs decrement

Method / Iterator Mutations (Rust)

OriginalMutatedTest Should Verify
.starts_with().ends_with()Correct string position
.ends_with().starts_with()Correct string

Content truncated.

When not to use it

  • When only code coverage metrics are needed
  • When tests are not yet written or are incomplete
  • When the goal is to write new tests from scratch

Limitations

  • Not every surviving mutant requires a new test
  • Some mutations may produce equivalent behavior
  • Focus is on changed code, not every line

How it compares

This approach actively verifies test suite reliable by simulating bugs, unlike passive code coverage which only measures execution paths.

Compared to similar skills

mutation-testing side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
mutation-testing (this skill)02moReviewIntermediate
python-testing-patterns772moReviewIntermediate
dependency-upgrade264moReviewIntermediate
test-cases576moNo flagsBeginner

Try saying

Example prompts that trigger this skill in your AI assistant.

Search skills

Search the agent skills registry