Hone logo
Hone
Problems

Jest Custom Matcher: Asserting Promise Resolution

In modern JavaScript development, asynchronous operations using Promises are ubiquitous. Jest, a popular testing framework, provides excellent support for testing Promises. However, sometimes you might need to make more specific assertions about how a Promise resolves, beyond simply checking if it resolves or rejects. This challenge involves creating a custom Jest matcher to assert that a Promise resolves with a specific value.

Problem Description

Your task is to implement a custom Jest matcher called resolvesWith for Jest. This matcher should be used to assert that a Promise resolves with a particular value. It should integrate seamlessly with Jest's existing assertion API.

Requirements:

  1. Create a Custom Matcher: Implement a function that registers a new Jest matcher named resolvesWith.
  2. Promise Resolution Check: The matcher should take a Promise as the received value and a target value as the expected value.
  3. Assertion Logic:
    • If the received value is not a Promise, the matcher should fail gracefully, indicating that it expected a Promise.
    • If the Promise resolves, the matcher should compare the resolved value with the expected value.
    • If the Promise rejects, the matcher should fail, indicating that the Promise was expected to resolve.
    • If the Promise resolves but with a different value than expected, the matcher should fail, showing the received and expected values.
  4. Clear Error Messages: Provide descriptive error messages for all failure cases.
  5. Integration: Ensure the matcher can be used with expect(promise).resolvesWith(expectedValue).

Expected Behavior:

  • expect(Promise.resolve(5)).resolvesWith(5) should pass.
  • expect(Promise.resolve(5)).resolvesWith(10) should fail with a message indicating the value mismatch.
  • expect(Promise.reject(new Error('Failed'))).resolvesWith(5) should fail with a message indicating the Promise rejected.
  • expect(someNonPromise).resolvesWith(5) should fail with a message indicating that a Promise was expected.

Examples

Example 1:

// In your test file:
import { expect } from '@jest/globals';
import './customMatchers'; // Assuming your custom matcher is registered here

test('should resolve with the correct value', async () => {
  const myPromise = Promise.resolve(42);
  await expect(myPromise).resolvesWith(42);
});

Output (if test passes): No output, test passes.

Explanation: The Promise.resolve(42) resolves with the value 42. The resolvesWith(42) matcher correctly asserts this.

Example 2:

// In your test file:
import { expect } from '@jest/globals';
import './customMatchers';

test('should fail if the resolved value is different', async () => {
  const myPromise = Promise.resolve('hello');
  // This assertion is expected to fail
  await expect(myPromise).resolvesWith('world');
});

Expected Output (if test fails):

    Expected promise to resolve with "world", but it resolved with "hello".

    Promise: Promise { <pending> }
    Received value: "hello"
    Expected value: "world"

Explanation: The Promise resolves with "hello", but the resolvesWith matcher expected "world". The error message clearly indicates the mismatch.

Example 3:

// In your test file:
import { expect } from '@jest/globals';
import './customMatchers';

test('should fail if the promise rejects', async () => {
  const myPromise = Promise.reject(new Error('Something went wrong'));
  // This assertion is expected to fail
  await expect(myPromise).resolvesWith(100);
});

Expected Output (if test fails):

    Expected promise to resolve, but it rejected with Error: Something went wrong.

    Promise: Promise { <rejected> Error: Something went wrong ... }

Explanation: The Promise rejects with an Error. The resolvesWith matcher correctly identifies this and reports that it expected a resolution, not a rejection.

Example 4:

// In your test file:
import { expect } from '@jest/globals';
import './customMatchers';

test('should fail if the received value is not a promise', () => {
  const notAPromise = 123;
  // This assertion is expected to fail
  expect(notAPromise).resolvesWith(123);
});

Expected Output (if test fails):

    Expected value to be a Promise, but received a number.

Explanation: The expect function received a number (123), not a Promise. The resolvesWith matcher correctly flags this type mismatch.

Constraints

  • The solution must be written in TypeScript.
  • You must use Jest's custom matcher API.
  • The matcher should handle both synchronous and asynchronous resolution of Promises.
  • The matcher should be efficient and not introduce significant overhead to test execution.

Notes

  • Refer to the Jest documentation on Extending Jest for guidance on creating custom matchers.
  • Consider how to handle the asynchronous nature of Promises within your matcher. The async/await keywords will be your friends here.
  • Think about the different states a Promise can be in (pending, fulfilled, rejected) and how your matcher should behave in each.
  • You'll need to register your custom matcher before running your tests. A common place is a setup file for Jest.
Loading editor...
typescript