Hone logo
Hone
Problems

Implement toBeCloseTo Matcher for Jest

Jest's expect().toBeCloseTo() matcher is invaluable for testing floating-point numbers, which can often have small precision errors due to their binary representation. This challenge asks you to implement a custom Jest matcher that mimics this behavior, allowing you to assert that a received number is close to an expected number within a specified precision.

Problem Description

You need to create a custom Jest matcher that checks if a received number is approximately equal to an expected number. This is particularly useful for comparing floating-point values where exact equality might fail due to minor precision discrepancies.

Key Requirements:

  • The matcher should be named toBeCloseTo.
  • It should accept one argument: the expected number.
  • Optionally, it can accept a second argument: numDigitsAfterDecimal, which specifies the number of decimal places to consider for the comparison. If not provided, a default precision should be used.
  • The matcher should return true if the received number is close to the expected number within the specified precision, and false otherwise.
  • It should provide informative failure messages for both passing and failing assertions.

Expected Behavior:

  • When numDigitsAfterDecimal is provided, the received number should be considered close if the absolute difference between received and expected is less than 0.5 * 10^(-numDigitsAfterDecimal).
  • If numDigitsAfterDecimal is not provided, a reasonable default precision should be used (e.g., comparing up to 2 decimal places).
  • The matcher should handle cases where received or expected are not numbers gracefully, though your primary focus is on numerical comparisons.

Edge Cases to Consider:

  • Negative numbers.
  • Zero.
  • Very small or very large numbers.
  • When numDigitsAfterDecimal is 0.
  • When received or expected are NaN, Infinity, or -Infinity.

Examples

Example 1:

// Test file: math.test.ts
import { expect } from '@jest/globals';
import './customMatchers'; // Assume your custom matcher is registered here

describe('toBeCloseTo', () => {
  it('should pass when numbers are close within default precision', () => {
    expect(2.123).toBeCloseTo(2.12);
  });

  it('should pass when numbers are close within specified precision', () => {
    expect(10.12345).toBeCloseTo(10.123, 3);
  });

  it('should fail when numbers are not close enough', () => {
    expect(3.14159).not.toBeCloseTo(3.14, 1);
  });
});

Output for Example 1 (Illustrative - actual Jest output will vary):

  • expect(2.123).toBeCloseTo(2.12); -> PASS
  • expect(10.12345).toBeCloseTo(10.123, 3); -> PASS
  • expect(3.14159).not.toBeCloseTo(3.14, 1); -> PASS (because the .not negates the failing assertion)

Example 2:

// Test file: math.test.ts (continued)
import { expect } from '@jest/globals';
import './customMatchers';

describe('toBeCloseTo edge cases', () => {
  it('should handle zero correctly', () => {
    expect(0.00001).toBeCloseTo(0, 4);
  });

  it('should handle negative numbers correctly', () => {
    expect(-5.6789).toBeCloseTo(-5.679, 3);
  });

  it('should handle precision of 0', () => {
    expect(15.7).toBeCloseTo(16, 0); // Difference is 0.3, less than 0.5 * 10^0 = 0.5
  });

  it('should fail for NaN', () => {
    expect(NaN).toBeCloseTo(5); // This assertion would fail and show a message
  });
});

Output for Example 2 (Illustrative):

  • expect(0.00001).toBeCloseTo(0, 4); -> PASS
  • expect(-5.6789).toBeCloseTo(-5.679, 3); -> PASS
  • expect(15.7).toBeCloseTo(16, 0); -> PASS
  • expect(NaN).toBeCloseTo(5); -> FAIL (with a message indicating NaN is not a number or close)

Constraints

  • The received and expected arguments will typically be numbers (integers or floats).
  • numDigitsAfterDecimal will be a non-negative integer.
  • Your matcher implementation should be efficient and not introduce significant overhead.
  • You are expected to integrate this as a custom Jest matcher.

Notes

  • Recall how to extend Jest's expect object with custom matchers.
  • The core logic will involve calculating the absolute difference and comparing it against a threshold derived from numDigitsAfterDecimal.
  • Consider using Math.abs() for the difference calculation.
  • Think about how to construct informative failure messages that include the received and expected values, and the precision used.
  • For handling non-numeric inputs or NaN/Infinity, you can either throw an error or return false with an appropriate message, depending on how robust you want the matcher to be. The primary goal is accurate floating-point comparison.
  • A good default precision for numDigitsAfterDecimal could be 2, as this is often a common requirement for many floating-point comparisons.
Loading editor...
typescript