# Mutation Testing with AI Agents When Stryker Doesn't Work



## The Coverage Lie

Code coverage lies. A test that exercises a line doesn't mean it verifies that line does the right thing:

```typescript
function add(a: number, b: number): number {
  return a + b
}

// 100% coverage - would still pass if add() returned 999
it('adds numbers', () => {
  add(2, 2)
})
```

Mutation testing flips the question. Instead of asking "did tests run this code?", it asks **"if I break this code, do tests fail?"**

Using our `add` example, a mutation tester would:

```typescript
// Original
function add(a: number, b: number): number {
  return a + b
}

// Mutated: swap + for -
function add(a: number, b: number): number {
  return a - b  // <-- bug introduced
}
```

Now run the test. `add(2, 2)` returns `0` instead of `4`. Does the test fail? No—it never checked the result. **The mutant survives.** Your test has a gap.

The process:
1. **Mutate**: Introduce a small bug (change `>` to `>=`, swap `&&` for `||`, delete a line)
2. **Run tests**: Execute your test suite against the mutated code
3. **Evaluate**: If tests pass with the bug, your tests are weak. If tests fail, they caught it.

A mutation that tests fail to catch is a "surviving mutant"—proof of a test gap.

---

## When Stryker Works: The Gold Standard

When your test stack supports it, automated mutation testing with Stryker is the way to go. It's fast, deterministic, generates HTML reports, and runs in CI pipelines. This is especially valuable when you have pure functions with high test coverage but want to verify test quality.

Here's what it looks like in practice:

```bash
pnpm test:mutation