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
expectednumber. - 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
trueif thereceivednumber is close to theexpectednumber within the specified precision, andfalseotherwise. - It should provide informative failure messages for both passing and failing assertions.
Expected Behavior:
- When
numDigitsAfterDecimalis provided, thereceivednumber should be considered close if the absolute difference betweenreceivedandexpectedis less than0.5 * 10^(-numDigitsAfterDecimal). - If
numDigitsAfterDecimalis not provided, a reasonable default precision should be used (e.g., comparing up to 2 decimal places). - The matcher should handle cases where
receivedorexpectedare 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
numDigitsAfterDecimalis 0. - When
receivedorexpectedareNaN,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);-> PASSexpect(10.12345).toBeCloseTo(10.123, 3);-> PASSexpect(3.14159).not.toBeCloseTo(3.14, 1);-> PASS (because the.notnegates 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);-> PASSexpect(-5.6789).toBeCloseTo(-5.679, 3);-> PASSexpect(15.7).toBeCloseTo(16, 0);-> PASSexpect(NaN).toBeCloseTo(5);-> FAIL (with a message indicating NaN is not a number or close)
Constraints
- The
receivedandexpectedarguments will typically be numbers (integers or floats). numDigitsAfterDecimalwill 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
expectobject 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
receivedandexpectedvalues, and the precision used. - For handling non-numeric inputs or
NaN/Infinity, you can either throw an error or returnfalsewith 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
numDigitsAfterDecimalcould be 2, as this is often a common requirement for many floating-point comparisons.