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:
- Create a Custom Matcher: Implement a function that registers a new Jest matcher named
resolvesWith. - Promise Resolution Check: The matcher should take a Promise as the received value and a target value as the expected value.
- 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.
- Clear Error Messages: Provide descriptive error messages for all failure cases.
- 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/awaitkeywords 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.