Implementing Mutation Operators for Jest Tests
Mutation testing is a powerful technique for evaluating the quality of your test suite. It involves making small changes (mutations) to your source code and then running your tests. If your tests fail after a mutation, it means your tests are effectively catching bugs. This challenge focuses on creating custom mutation operators that can be used with Jest.
Problem Description
Your task is to implement a set of custom mutation operators that can be integrated with Jest's testing framework. These operators will programmatically alter your source code in specific ways, simulating potential bugs. The goal is to provide a foundation for building a mutation testing tool for TypeScript projects using Jest.
Key Requirements:
- Operator Implementation: Create functions for at least three distinct mutation operators. These operators should take a code snippet (as a string) and return mutated versions of that snippet.
- Operator Types: Implement operators that cover common code modifications:
- Arithmetic Operator Replacement: Replace arithmetic operators (+, -, *, /, %) with equivalent or related operators.
- Logical Operator Replacement: Replace logical operators (&&, ||, !, ===, !==, >, <, >=, <=) with equivalent or related operators.
- Number Literal Replacement: Replace numeric literals with other common numeric values (e.g., 0, 1, -1, a slightly larger or smaller number).
- AST Manipulation (Conceptual): While you won't be building a full AST parser from scratch for this challenge, your operators should conceptually understand how to target and modify specific code constructs. For this exercise, you can use regular expressions or simple string manipulation to achieve the mutations, assuming a well-defined input format.
- Jest Integration (Conceptual): Understand how these operators would be used in a Jest context. The output of your operators will be the mutated code, which a hypothetical Jest runner would then compile and test. You are not required to set up Jest or run actual tests.
Expected Behavior:
For a given input code string, each operator should produce one or more output strings, each representing a mutated version of the input.
Examples
Example 1: Arithmetic Operator Replacement
Input: "return a + b;"
Output: [
"return a - b;",
"return a * b;",
"return a / b;",
"return a % b;"
]
Explanation: The '+' operator is replaced by '-', '*', '/', and '%'.
Example 2: Logical Operator Replacement
Input: "if (x > 5 && y < 10)"
Output: [
"if (x < 5 && y < 10)", // '>' replaced by '<'
"if (x >= 5 && y < 10)", // '>' replaced by '>='
"if (x === 5 && y < 10)", // '>' replaced by '===' (less common but possible for demonstration)
"if (x > 5 || y < 10)" // '&&' replaced by '||'
]
Explanation: Demonstrates replacing a comparison operator and a logical operator.
Example 3: Number Literal Replacement
Input: "const count = 10;"
Output: [
"const count = 0;",
"const count = 1;",
"const count = -1;",
"const count = 9;" // or "const count = 11;"
]
Explanation: The number literal '10' is replaced with 0, 1, -1, and a slightly altered value.
Constraints
- The input to your mutation operator functions will be a single string representing a TypeScript code snippet.
- Your output should be an array of strings, where each string is a mutated version of the input.
- Focus on the logic of generating mutations, not on building a full-fledged parser or Jest plugin.
- Assume simple, well-formed input code snippets for the purpose of demonstrating operator logic. Complex nested structures or esoteric syntax might not be perfectly handled by simple string manipulation.
Notes
- Think about how you would accurately identify the parts of the code you want to mutate. Regular expressions can be useful here, but be mindful of their limitations.
- Consider the "equivalent" or "related" operators for replacements. For example, replacing
>with<is a valid mutation. Replacing+with*also makes sense. - For number literal replacement, consider replacing with 0, 1, -1, and perhaps a small delta. You don't need to replace every possible number.
- The goal is to create functions that generate mutations. You are not expected to run these mutations against actual code or tests.