Hone logo
Hone
Problems

Mutant Testing with Jest: Strengthening Your Code's Resilience

Mutation testing is a powerful technique for evaluating the quality of your unit tests. It involves introducing small, deliberate changes (mutations) to your source code and then running your test suite. If your tests fail to detect these mutations, it indicates that your tests might not be robust enough to catch real-world bugs. This challenge will guide you in implementing a basic mutant testing workflow using Jest.

Problem Description

Your task is to implement a process that simulates mutation testing within a Jest environment. This will involve:

  1. Writing a simple function that you will then "mutate."
  2. Writing comprehensive Jest tests for this function.
  3. Manually simulating mutations to the function.
  4. Observing how your Jest tests behave when faced with these mutated versions of the code.

The goal is to understand how changes in the source code affect test outcomes and to identify areas where your tests might be insufficient.

Examples

Let's consider a simple sum function.

Example 1: Original Function and Passing Tests

// src/math.ts
export function sum(a: number, b: number): number {
  return a + b;
}
// src/math.test.ts
import { sum } from './math';

describe('sum', () => {
  test('should return the correct sum for positive numbers', () => {
    expect(sum(2, 3)).toBe(5);
  });

  test('should return the correct sum for negative numbers', () => {
    expect(sum(-2, -3)).toBe(-5);
  });

  test('should return the correct sum when one number is zero', () => {
    expect(sum(5, 0)).toBe(5);
    expect(sum(0, 5)).toBe(5);
  });
});

Explanation: The original sum function correctly adds two numbers. The provided Jest tests cover positive numbers, negative numbers, and cases involving zero, and all tests pass.

Example 2: A Simple Mutation and Failing Test

Imagine we mutate src/math.ts by changing the + operator to -:

// src/math.ts (Mutated)
export function sum(a: number, b: number): number {
  return a - b; // Mutated operator
}

Expected Test Behavior: When you run the src/math.test.ts against this mutated src/math.ts, you would expect at least one test to fail. Specifically, the test should return the correct sum for positive numbers would fail: sum(2, 3) would return -1, not 5.

Explanation: The mutation introduced a bug (subtraction instead of addition). The existing tests are sensitive enough to detect this bug, causing a test failure, which signifies that the mutant has been "killed."

Example 3: A Mutation and a "Surviving" Mutant

Consider another mutation to the original sum function:

// src/math.ts (Mutated)
export function sum(a: number, b: number): number {
  return a + b + 0; // Mutated by adding zero
}

Expected Test Behavior: When you run src/math.test.ts against this second mutated src/math.ts, all tests would likely still pass.

Explanation: While the code has been technically changed, the addition of 0 does not alter the mathematical outcome of the sum operation. Therefore, the existing tests are not sensitive enough to detect this change, and the mutant "survives." This highlights a potential weakness in the test suite; it doesn't explicitly test for this kind of redundant operation.

Constraints

  • You will be working with TypeScript.
  • You will be using Jest as your testing framework.
  • For this challenge, you will be manually simulating mutations. We are not implementing an automated mutation testing tool like Stryker Mutator.
  • The focus is on understanding the concept and manual application of killing mutants.
  • Your source code should be placed in a src directory, and your tests in a src directory (e.g., src/math.ts and src/math.test.ts).

Notes

  • To effectively simulate mutations, consider changing operators (e.g., + to -, * to /, > to >=), modifying return values slightly (e.g., adding 0 or 1 to a result), or changing conditional logic (e.g., true to false, > to <).
  • The goal is to demonstrate your understanding of how tests react to code changes.
  • Think about what makes a test "strong" – it should be specific enough to catch logical errors introduced by mutations.
  • You might want to create multiple versions of your source file (e.g., src/math_mutated_1.ts, src/math_mutated_2.ts) to experiment with different mutations and their impact on your tests. Remember to adjust your import statements in the test file accordingly for each scenario.
Loading editor...
typescript