Hone logo
Hone
Problems

Jest Custom Matcher: Implementing toReject

Jest is a powerful JavaScript testing framework that provides a rich set of built-in matchers. However, there are times when you need to assert specific behaviors related to asynchronous operations, particularly Promises that are expected to reject. This challenge focuses on creating a custom Jest matcher that specifically checks if a Promise rejects with a certain error or satisfies a condition.

Problem Description

Your task is to implement a custom Jest matcher named toReject. This matcher should be able to assert that a given Promise rejects. You will need to handle two primary scenarios:

  1. Rejecting with a specific error or value: The matcher should accept an optional argument, which can be an error instance, a regular expression, or a function. If provided, the matcher should verify that the rejected Promise's reason matches this argument.
  2. Rejecting without a specific argument: If no argument is provided, the matcher should simply assert that the Promise rejects, regardless of the rejection reason.

Key Requirements:

  • The matcher should be named toReject.
  • It must correctly handle Promises that resolve (should fail the assertion).
  • It must correctly handle Promises that reject.
  • If an argument is provided to toReject:
    • If the argument is an Error instance, the rejection reason must be an instance of that error.
    • If the argument is a RegExp, the rejection reason's message property (if it exists and is a string) must match the regular expression.
    • If the argument is a function, this function will be called with the rejection reason. The matcher should pass if this function returns true.
  • The matcher should provide informative failure messages for both positive (expect(promise).toReject()) and negative (expect(promise).not.toReject()) assertions.
  • The implementation should be in TypeScript.

Examples

Example 1: Basic Rejection

// Inside your test file:
const promiseThatRejects = Promise.reject(new Error('Something went wrong'));

test('should reject', async () => {
  await expect(promiseThatRejects).toReject();
});

Example 2: Rejecting with a Specific Error Instance

// Inside your test file:
const specificError = new TypeError('Invalid type provided');
const promiseThatRejectsWithSpecificError = Promise.reject(specificError);

test('should reject with a specific error', async () => {
  await expect(promiseThatRejectsWithSpecificError).toReject(TypeError);
});

Example 3: Rejecting with an Error Matching a RegExp

// Inside your test file:
const promiseThatRejectsWithSpecificMessage = Promise.reject(new Error('User not found. ID: 123'));

test('should reject with an error message matching a regex', async () => {
  await expect(promiseThatRejectsWithSpecificMessage).toReject(/User not found/);
});

Example 4: Rejecting with a Custom Condition (Function)

// Inside your test file:
interface CustomError {
  code: number;
  message: string;
}
const promiseThatRejectsWithCustomError = Promise.reject<string, CustomError>({ code: 404, message: 'Resource not available' });

test('should reject with a custom error object satisfying a condition', async () => {
  await expect(promiseThatRejectsWithCustomError).toReject((error) => {
    if (typeof error === 'object' && error !== null && 'code' in error) {
      const customError = error as CustomError;
      return customError.code === 404;
    }
    return false;
  });
});

Example 5: Promise Resolves (Failing Assertion)

// Inside your test file:
const promiseThatResolves = Promise.resolve('Success!');

test('should fail if promise resolves', async () => {
  // This assertion should fail
  // await expect(promiseThatResolves).toReject();
});

Constraints

  • The implementation must be compatible with Jest v27+ and TypeScript.
  • Your solution should be delivered as a Jest custom matcher.
  • Avoid using external libraries for Promise manipulation beyond standard JavaScript.

Notes

  • Remember that Jest custom matchers are asynchronous by nature when dealing with Promises. You'll likely need to use async/await in your matcher implementation.
  • Consider how to properly handle the rejection reason. It could be an Error object, a primitive value, or a complex object.
  • Think about how to construct clear and helpful failure messages. These messages are crucial for debugging failing tests.
  • You will need to declare the custom matcher type for TypeScript to recognize it. You can typically do this by extending JestMatchers<T> or Global interfaces.
Loading editor...
typescript