Implement a Custom Jest Matcher: toMatchObject
Jest is a popular JavaScript testing framework that provides a rich set of built-in matchers for assertions. One such matcher is toMatchObject, which allows you to assert that an object contains a subset of properties and their values. This challenge asks you to implement a custom version of toMatchObject in TypeScript for Jest.
Problem Description
You need to create a Jest custom matcher named toMatchObject that checks if a received object contains all the properties and their corresponding values specified in the expected object. This is useful for testing components or functions that return complex objects, where you might only care about a specific subset of the returned data.
Key Requirements:
- The matcher should accept an
expectedobject as an argument. - It should compare the
receivedobject (the actual object being tested) against theexpectedobject. - The matcher should pass if, for every key-value pair in
expected, thereceivedobject has the same key and the same value. - The matcher should fail if any key from
expectedis missing inreceived, or if the value associated with a key inexpecteddoes not match the value inreceived. - Nested objects should also be compared recursively. If a property in
expectedis an object, the corresponding property inreceivedmust also be an object and contain all the properties of the nestedexpectedobject. - Arrays are compared by reference. If an array is expected, the received value must be the exact same array instance. This mimics Jest's default behavior for
toMatchObject.
Expected Behavior:
- If
receivedcontains all the properties and values ofexpected(including nested object comparisons), the matcher should report success. - If
receivedis missing any properties fromexpected, or if any values do not match, the matcher should report failure with a clear message indicating what failed (e.g., missing key, mismatched value, mismatched nested object).
Edge Cases to Consider:
expectedobject is empty: Should always pass.receivedobject is empty: Should pass only ifexpectedis also empty.receivedobject has extra properties not present inexpected: This should not cause the matcher to fail; only properties inexpectedneed to be present and match.nullorundefinedvalues inexpectedorreceived.- Non-object types in
expectedorreceived(e.g., primitives, arrays,null).
Examples
Example 1: Basic Object Matching
Input:
received: { name: 'Alice', age: 30, city: 'New York' }
expected: { name: 'Alice', age: 30 }
Output: Pass
Explanation: The received object contains both name and age with matching values. The extra city property in received is ignored.
Example 2: Mismatched Value
Input:
received: { name: 'Alice', age: 30 }
expected: { name: 'Alice', age: 25 }
Output: Fail
Explanation: The age property in expected is 25, but in received it is 30.
Example 3: Missing Property
Input:
received: { name: 'Alice' }
expected: { name: 'Alice', age: 30 }
Output: Fail
Explanation: The age property is missing from the received object.
Example 4: Nested Object Matching
Input:
received: { user: { id: 1, name: 'Bob', address: { street: '123 Main St', zip: '10001' } } }
expected: { user: { name: 'Bob', address: { street: '123 Main St' } } }
Output: Pass
Explanation: The user object in received contains a name matching expected, and its nested address object contains a street matching expected. The id in received.user and zip in received.user.address are ignored.
Example 5: Mismatched Nested Object
Input:
received: { user: { id: 1, name: 'Bob', address: { street: '123 Main St', zip: '10001' } } }
expected: { user: { name: 'Bob', address: { street: '456 Oak Ave' } } }
Output: Fail
Explanation: The street property within the nested address object does not match.
Example 6: Array Comparison (Reference Equality)
Input:
received: { items: [1, 2, 3] }
expected: { items: [1, 2, 3] }
Output: Fail
Explanation: Jest's toMatchObject for arrays checks for reference equality. Since these are two different array instances, the matcher fails. If expected was received.items, it would pass.
Constraints
- The implementation should be written in TypeScript.
- The custom matcher should be compatible with Jest's custom matcher API.
- The comparison should handle up to 10 levels of nested objects.
- The number of properties in an object can be up to 100.
Notes
- You'll need to define a Jest custom matcher. This typically involves extending
jest.Expect. - Consider how you will recursively traverse the
expectedandreceivedobjects. - Think about how to generate informative failure messages.
- Remember that
receivedmight not be an object or might benullorundefined. Your matcher should handle these cases gracefully. - The behavior with arrays is crucial to get right; it's reference equality, not deep equality.