Contract Matching with Jest
Ensuring your functions adhere to a defined contract is crucial for maintainable and reliable code. This challenge focuses on creating a utility function that validates if a function's output matches a predefined contract (a set of key-value pairs) and then writing Jest tests to verify its behavior. This is particularly useful for API integrations or complex calculations where you want to guarantee specific output structures.
Problem Description
You need to create a TypeScript function called matchesContract that takes three arguments:
actual: The actual value returned by the function you're testing. This can be any JavaScript/TypeScript data type (object, array, string, number, etc.).contract: An object representing the expected contract. The keys of this object are the expected keys in theactualobject, and the values are the expected values for those keys.message: An optional string to be used as a custom error message if the contract doesn't match.
The matchesContract function should return true if the actual value matches the contract and false otherwise. If the contract doesn't match, it should throw an error with the provided message (or a default message if none is provided).
Key Requirements:
- The function must handle various data types for both
actualandcontract. - The function should perform a deep comparison of the values. Simple
===comparison is not sufficient for objects and arrays. - The function should throw an error if the contract doesn't match.
- The function should handle cases where the
contractobject has keys that are not present in theactualobject.
Expected Behavior:
- If
actualandcontractare identical objects, the function should returntrue. - If
actualandcontractare different objects, the function should returnfalseand throw an error. - If
actualis a primitive value (string, number, boolean) andcontractis an object with a single key-value pair where the key is the string representation of the primitive and the value is the primitive itself, the function should returntrue. Otherwise, it should returnfalseand throw an error. - If
contractis empty, the function should returntrueregardless of theactualvalue.
Edge Cases to Consider:
actualisnullorundefined.contractisnullorundefined.actualandcontractcontain nested objects or arrays.actualcontains keys that are not present incontract.contractcontains keys that are not present inactual.- Circular references in
actualorcontract(should be handled gracefully, ideally without infinite loops).
Examples
Example 1:
Input: actual = { name: 'John', age: 30 }, contract = { name: 'John', age: 30 }, message = 'Contract mismatch'
Output: true
Explanation: The actual object matches the contract object exactly.
Example 2:
Input: actual = { name: 'John', age: 30 }, contract = { name: 'Jane', age: 30 }, message = 'Contract mismatch'
Output: Error: Contract mismatch
Explanation: The 'name' property in the actual object does not match the contract.
Example 3:
Input: actual = { name: 'John', age: 30, city: 'New York' }, contract = { name: 'John', age: 30 }, message = 'Contract mismatch'
Output: Error: Contract mismatch
Explanation: The actual object has an extra property ('city') that is not in the contract.
Example 4:
Input: actual = 123, contract = { value: 123 }, message = 'Contract mismatch'
Output: true
Explanation: The actual value matches the contract's value.
Example 5:
Input: actual = { a: [1, 2, 3] }, contract = { a: [1, 2, 3] }, message = 'Contract mismatch'
Output: true
Explanation: Nested array comparison works correctly.
Constraints
- The
actualandcontractobjects can contain nested objects and arrays. - The
matchesContractfunction should be performant enough to be used in Jest tests without causing significant slowdowns. Avoid excessively complex or inefficient deep comparison algorithms. - The
contractobject will always be a plain JavaScript object. - The
messageargument is optional and can be any string.
Notes
- Consider using a library like
lodashorfast-deep-equalfor deep comparison if you're not comfortable implementing it yourself. However, for this challenge, implementing your own deep comparison logic is encouraged to demonstrate understanding. - Think about how to handle circular references to prevent infinite loops during deep comparison. A simple check for previously visited objects can be effective.
- Focus on creating a robust and reliable function that can handle a wide range of input scenarios. Thorough testing with Jest is essential.
- The goal is to create a function that can be easily integrated into Jest tests to validate function contracts.