
Mutation Testing
- 422 installs
- 305 repo stars
- Updated March 4, 2026
- aj-geddes/useful-ai-prompts
mutation-testing is an agent skill that runs mutation testing with Stryker, PITest, or mutmut to expose weak unit tests for developers who need behavioral test confidence before release.
About
mutation-testing is an agent skill from aj-geddes/useful-ai-prompts that evaluates unit test effectiveness by injecting small code mutations and checking whether tests fail. It explains mutation concepts including killed, survived, equivalent, and timed-out mutants, plus mutation operators for arithmetic, conditional, and boundary changes. The skill provides tool-specific guidance for Stryker on JavaScript and TypeScript, PITest on Java, mutmut on Python, and Infection on PHP, including setup, CI integration, and report interpretation. Developers reach for mutation-testing when line coverage looks high but tests may not verify real behavior, or before release on critical modules like billing, auth, or pricing logic. It recommends targeting critical paths rather than entire repositories and aiming for roughly 80 percent mutation scores on important code. Output is mutation reports, survived-mutant analysis, and strengthened test assertions rather than new application features.
- Mutant generation
- Kill rate analysis
- Weak assertion detection
- CI integration patterns
- Coverage vs quality gap
Mutation Testing by the numbers
- 422 all-time installs (skills.sh)
- Ranked #642 of 2,153 Testing & QA skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/aj-geddes/useful-ai-prompts --skill mutation-testingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 422 |
|---|---|
| repo stars | ★ 305 |
| Last updated | March 4, 2026 |
| Repository | aj-geddes/useful-ai-prompts ↗ |
How do you find weak unit tests with mutation testing?
Run mutation testing to find weak unit tests, improve assertions, and raise confidence before release by verifying tests catch injected faults.
Who is it for?
Developers with existing unit tests who need to verify tests catch real bugs beyond line coverage metrics before release.
Skip if: Greenfield projects without tests yet or teams wanting fast full-repo mutation runs on every CI build without scoping critical modules.
When should I use this skill?
User asks for mutation testing, Stryker setup, mutmut runs, test effectiveness analysis, or wants to find weak assertions before release.
What you get
Mutation testing reports, mutation scores, survived-mutant analysis, and improved test assertions that kill injected faults.
- mutation test report
- survived-mutant list
- strengthened test cases
By the numbers
- Covers 4 mutation testing tools: Stryker, PITest, mutmut, and Infection
- Recommends roughly 80% mutation score target on critical code modules
Files
Mutation Testing
Table of Contents
Overview
Mutation testing assesses test suite quality by introducing small changes (mutations) to source code and verifying that tests fail. If tests don't catch a mutation, it indicates gaps in test coverage or test quality. This technique helps identify weak or ineffective tests.
When to Use
- Evaluating test suite effectiveness
- Finding untested code paths
- Improving test quality metrics
- Validating critical business logic is well-tested
- Identifying redundant or weak tests
- Measuring real test coverage beyond line coverage
- Ensuring tests actually verify behavior
Quick Start
Minimal working example:
# Install Stryker
npm install --save-dev @stryker-mutator/core @stryker-mutator/jest-runner
# Initialize configuration
npx stryker init
# Run mutation testing
npx stryker runReference Guides
Detailed implementations in the references/ directory:
| Guide | Contents |
|---|---|
| Stryker for JavaScript/TypeScript | Stryker for JavaScript/TypeScript |
| PITest for Java | PITest for Java |
| mutmut for Python | mutmut for Python |
| Mutation Testing Reports | Mutation Testing Reports |
Best Practices
✅ DO
- Target critical business logic for mutation testing
- Aim for 80%+ mutation score on important code
- Review survived mutants to improve tests
- Mark equivalent mutants to exclude them
- Use mutation testing in CI for critical modules
- Test boundary conditions thoroughly
- Verify actual behavior, not just code execution
❌ DON'T
- Expect 100% mutation score everywhere
- Run mutation testing on all code (too slow)
- Ignore equivalent mutants
- Test getters/setters with mutations
- Run mutations on generated code
- Skip mutation testing on complex logic
- Focus only on line coverage
Mutation Testing Reports
Mutation Testing Reports
# Stryker HTML report shows:
# - Mutation score: 85.5%
# - Mutants killed: 94
# - Mutants survived: 16
# - Mutants timeout: 0
# - Mutants no coverage: 10
# Example mutations:
# ❌ Survived: Changed > to >= in isPositive
# No test checks boundary condition
#
# ✅ Killed: Changed + to - in add method
# Test expects specific result
#
# ❌ Survived: Removed if condition check
# Missing test for that edge casemutmut for Python
mutmut for Python
# Install mutmut
pip install mutmut
# Run mutation testing
mutmut run
# Show results
mutmut results
# Show specific mutant
mutmut show 1
# Apply mutation to see what changed
mutmut apply 1# src/string_utils.py
def is_palindrome(s: str) -> bool:
"""Check if string is palindrome."""
clean = ''.join(c.lower() for c in s if c.isalnum())
return clean == clean[::-1]
def count_words(text: str) -> int:
"""Count words in text."""
if not text:
return 0
return len(text.split())
def truncate(text: str, max_length: int) -> str:
"""Truncate text to max length."""
if len(text) <= max_length:
return text
return text[:max_length] + "..."
# ❌ Weak tests
def test_palindrome_basic():
"""Weak: Only tests one case."""
assert is_palindrome("racecar") == True
# ✅ Strong tests that will catch mutations
def test_is_palindrome_simple():
assert is_palindrome("racecar") == True
assert is_palindrome("hello") == False
def test_is_palindrome_with_spaces():
assert is_palindrome("race car") == True
assert is_palindrome("not a palindrome") == False
def test_is_palindrome_with_punctuation():
assert is_palindrome("A man, a plan, a canal: Panama") == True
def test_is_palindrome_case_insensitive():
assert is_palindrome("RaceCar") == True
assert is_palindrome("Racecar") == True
def test_is_palindrome_empty():
assert is_palindrome("") == True
def test_is_palindrome_single_char():
assert is_palindrome("a") == True
def test_count_words_basic():
assert count_words("hello world") == 2
assert count_words("one") == 1
def test_count_words_multiple_spaces():
assert count_words("hello world") == 2
assert count_words(" leading spaces") == 2
def test_count_words_empty():
assert count_words("") == 0
assert count_words(" ") == 0
def test_truncate_short_text():
assert truncate("hello", 10) == "hello"
def test_truncate_exact_length():
assert truncate("hello", 5) == "hello"
def test_truncate_long_text():
result = truncate("hello world", 5)
assert result == "hello..."
assert len(result) == 8 # 5 + "..."
def test_truncate_zero_length():
assert truncate("hello", 0) == "..."PITest for Java
PITest for Java
<!-- pom.xml -->
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
<version>1.14.2</version>
<configuration>
<targetClasses>
<param>com.example.service.*</param>
</targetClasses>
<targetTests>
<param>com.example.service.*Test</param>
</targetTests>
<mutators>
<mutator>DEFAULTS</mutator>
</mutators>
<outputFormats>
<outputFormat>HTML</outputFormat>
<outputFormat>XML</outputFormat>
</outputFormats>
<timestampedReports>false</timestampedReports>
<mutationThreshold>80</mutationThreshold>
<coverageThreshold>90</coverageThreshold>
</configuration>
</plugin># Run mutation testing
mvn org.pitest:pitest-maven:mutationCoverage// src/main/java/OrderValidator.java
public class OrderValidator {
public boolean isValidOrder(Order order) {
if (order == null) {
return false;
}
if (order.getItems().isEmpty()) {
return false;
}
if (order.getTotal() <= 0) {
return false;
}
return true;
}
public double calculateDiscount(double total, String customerTier) {
if (customerTier.equals("GOLD")) {
return total * 0.2;
} else if (customerTier.equals("SILVER")) {
return total * 0.1;
}
return 0;
}
public int categorizeOrderSize(int itemCount) {
if (itemCount <= 5) {
return 1; // Small
} else if (itemCount <= 20) {
return 2; // Medium
} else {
return 3; // Large
}
}
}
// ❌ Weak tests that allow mutations to survive
@Test
public void testOrderValidation_Weak() {
OrderValidator validator = new OrderValidator();
Order order = new Order();
order.addItem(new Item("Product", 10.0));
order.setTotal(10.0);
// Only tests one scenario
assertTrue(validator.isValidOrder(order));
}
// ✅ Strong tests that kill mutations
public class OrderValidatorTest {
private OrderValidator validator;
@Before
public void setUp() {
validator = new OrderValidator();
}
@Test
public void isValidOrder_withNullOrder_returnsFalse() {
assertFalse(validator.isValidOrder(null));
}
@Test
public void isValidOrder_withEmptyItems_returnsFalse() {
Order order = new Order();
order.setTotal(10.0);
assertFalse(validator.isValidOrder(order));
}
@Test
public void isValidOrder_withZeroTotal_returnsFalse() {
Order order = new Order();
order.addItem(new Item("Product", 0));
order.setTotal(0);
assertFalse(validator.isValidOrder(order));
}
@Test
public void isValidOrder_withNegativeTotal_returnsFalse() {
Order order = new Order();
order.addItem(new Item("Product", -10.0));
order.setTotal(-10.0);
assertFalse(validator.isValidOrder(order));
}
@Test
public void isValidOrder_withValidOrder_returnsTrue() {
Order order = new Order();
order.addItem(new Item("Product", 10.0));
order.setTotal(10.0);
assertTrue(validator.isValidOrder(order));
}
@Test
public void calculateDiscount_goldTier_returns20Percent() {
assertEquals(20.0, validator.calculateDiscount(100.0, "GOLD"), 0.01);
}
@Test
public void calculateDiscount_silverTier_returns10Percent() {
assertEquals(10.0, validator.calculateDiscount(100.0, "SILVER"), 0.01);
}
@Test
public void calculateDiscount_regularTier_returnsZero() {
assertEquals(0.0, validator.calculateDiscount(100.0, "BRONZE"), 0.01);
}
@Test
public void categorizeOrderSize_smallOrder() {
assertEquals(1, validator.categorizeOrderSize(3));
assertEquals(1, validator.categorizeOrderSize(5));
}
@Test
public void categorizeOrderSize_mediumOrder() {
assertEquals(2, validator.categorizeOrderSize(6));
assertEquals(2, validator.categorizeOrderSize(20));
}
@Test
public void categorizeOrderSize_largeOrder() {
assertEquals(3, validator.categorizeOrderSize(21));
assertEquals(3, validator.categorizeOrderSize(100));
}
// Test boundary conditions
@Test
public void categorizeOrderSize_boundaries() {
assertEquals(1, validator.categorizeOrderSize(5));
assertEquals(2, validator.categorizeOrderSize(6));
assertEquals(2, validator.categorizeOrderSize(20));
assertEquals(3, validator.categorizeOrderSize(21));
}
}Stryker for JavaScript/TypeScript
Stryker for JavaScript/TypeScript
# Install Stryker
npm install --save-dev @stryker-mutator/core @stryker-mutator/jest-runner
# Initialize configuration
npx stryker init
# Run mutation testing
npx stryker run// stryker.conf.json
{
"$schema": "./node_modules/@stryker-mutator/core/schema/stryker-schema.json",
"packageManager": "npm",
"reporters": ["html", "clear-text", "progress", "dashboard"],
"testRunner": "jest",
"jest": {
"projectType": "custom",
"configFile": "jest.config.js",
"enableFindRelatedTests": true
},
"coverageAnalysis": "perTest",
"mutate": [
"src/**/*.ts",
"!src/**/*.spec.ts",
"!src/**/*.test.ts"
],
"thresholds": {
"high": 80,
"low": 60,
"break": 50
}
}
// Example source code
// src/calculator.ts
export class Calculator {
add(a: number, b: number): number {
return a + b;
}
subtract(a: number, b: number): number {
return a - b;
}
multiply(a: number, b: number): number {
return a * b;
}
divide(a: number, b: number): number {
if (b === 0) {
throw new Error('Division by zero');
}
return a / b;
}
isPositive(n: number): boolean {
return n > 0;
}
}
// ❌ Weak tests - mutations will survive
describe('Calculator - Weak Tests', () => {
const calc = new Calculator();
test('add returns a number', () => {
const result = calc.add(2, 3);
expect(typeof result).toBe('number');
// This test won't catch mutations like: return a - b; or return a * b;
});
test('divide with non-zero divisor', () => {
expect(() => calc.divide(10, 2)).not.toThrow();
// Doesn't verify the actual result!
});
});
// ✅ Strong tests - will kill mutations
describe('Calculator - Strong Tests', () => {
const calc = new Calculator();
describe('add', () => {
test('adds two positive numbers', () => {
expect(calc.add(2, 3)).toBe(5);
});
test('adds negative numbers', () => {
expect(calc.add(-2, -3)).toBe(-5);
});
test('adds zero', () => {
expect(calc.add(5, 0)).toBe(5);
expect(calc.add(0, 5)).toBe(5);
});
});
describe('subtract', () => {
test('subtracts numbers correctly', () => {
expect(calc.subtract(5, 3)).toBe(2);
expect(calc.subtract(3, 5)).toBe(-2);
});
});
describe('multiply', () => {
test('multiplies numbers', () => {
expect(calc.multiply(3, 4)).toBe(12);
expect(calc.multiply(-2, 3)).toBe(-6);
});
test('multiply by zero', () => {
expect(calc.multiply(5, 0)).toBe(0);
});
});
describe('divide', () => {
test('divides numbers correctly', () => {
expect(calc.divide(10, 2)).toBe(5);
expect(calc.divide(7, 2)).toBe(3.5);
});
test('throws error on division by zero', () => {
expect(() => calc.divide(10, 0)).toThrow('Division by zero');
});
});
describe('isPositive', () => {
test('returns true for positive numbers', () => {
expect(calc.isPositive(1)).toBe(true);
expect(calc.isPositive(100)).toBe(true);
});
test('returns false for zero and negative', () => {
expect(calc.isPositive(0)).toBe(false);
expect(calc.isPositive(-1)).toBe(false);
});
});
});#!/bin/bash
# scaffold-tests.sh - Generate test file scaffolding
# Usage: ./scaffold-tests.sh <source_file> [--framework jest|pytest|mocha]
set -euo pipefail
SOURCE_FILE="${{1:?Usage: $0 <source_file> [--framework jest|pytest|mocha]}}"
FRAMEWORK="${{2:-jest}}"
echo "Scaffolding tests for: $SOURCE_FILE (framework: $FRAMEWORK)"
# TODO: Implement test scaffolding logic
# - Parse source file for exported functions/classes
# - Generate test stubs for each export
# - Include setup/teardown boilerplate
# - Add common assertion patterns
echo "Test scaffolding complete."
// Test Template
// TODO: Customize for your testing framework and project
describe('ModuleName', () => {
// Setup
beforeEach(() => {
// TODO: Add test setup
});
afterEach(() => {
// TODO: Add cleanup
});
describe('functionName', () => {
it('should handle the happy path', () => {
// TODO: Add assertion
});
it('should handle edge cases', () => {
// TODO: Add edge case tests
});
it('should handle errors gracefully', () => {
// TODO: Add error handling tests
});
});
});
Related skills
How it compares
Pick mutation-testing over code-coverage skills when line coverage is high but you need proof that tests detect injected behavioral faults.
FAQ
Which mutation testing tools does mutation-testing cover?
mutation-testing covers Stryker for JavaScript and TypeScript, PITest for Java, mutmut for Python, and Infection for PHP. The skill provides tool-specific setup, run commands, and report interpretation for each.
What mutation score should developers target?
mutation-testing recommends aiming for roughly 80% mutation score on critical business logic modules. Full-repository runs are discouraged because mutation testing is slow and most valuable on high-risk code paths.
What does a survived mutant indicate?
mutation-testing explains that a survived mutant means tests still pass after a code change, revealing a gap in assertions or boundary coverage. Survived mutants should be reviewed to add tests that verify actual behavior.