Hone logo
Hone
Problems

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:

  1. actual: The actual value returned by the function you're testing. This can be any JavaScript/TypeScript data type (object, array, string, number, etc.).
  2. contract: An object representing the expected contract. The keys of this object are the expected keys in the actual object, and the values are the expected values for those keys.
  3. 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 actual and contract.
  • 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 contract object has keys that are not present in the actual object.

Expected Behavior:

  • If actual and contract are identical objects, the function should return true.
  • If actual and contract are different objects, the function should return false and throw an error.
  • If actual is a primitive value (string, number, boolean) and contract is 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 return true. Otherwise, it should return false and throw an error.
  • If contract is empty, the function should return true regardless of the actual value.

Edge Cases to Consider:

  • actual is null or undefined.
  • contract is null or undefined.
  • actual and contract contain nested objects or arrays.
  • actual contains keys that are not present in contract.
  • contract contains keys that are not present in actual.
  • Circular references in actual or contract (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 actual and contract objects can contain nested objects and arrays.
  • The matchesContract function should be performant enough to be used in Jest tests without causing significant slowdowns. Avoid excessively complex or inefficient deep comparison algorithms.
  • The contract object will always be a plain JavaScript object.
  • The message argument is optional and can be any string.

Notes

  • Consider using a library like lodash or fast-deep-equal for 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.
Loading editor...
typescript