Hone logo
Hone
Problems

Mastering Jest: Crafting Custom Matchers for Enhanced Assertions

Testing is a cornerstone of robust software development, and Jest is a popular framework for writing JavaScript and TypeScript tests. While Jest provides a rich set of built-in matchers, there are times when you need to assert on specific, complex conditions that aren't covered by default. This challenge will guide you through the process of creating your own custom Jest matchers in TypeScript, empowering you to write more expressive and maintainable tests.

Problem Description

Your task is to create a custom Jest matcher named toHaveEachPropertyWithValue that checks if an array of objects has a specific property, and if that property's value matches a given expected value for every object in the array. This is particularly useful when you have an array of data structures and want to ensure a consistent field value across all of them, for instance, verifying that all items in a user list have the same status.

Key Requirements:

  1. Matcher Name: The custom matcher should be named toHaveEachPropertyWithValue.
  2. Arguments: The matcher should accept two arguments:
    • propertyName: A string representing the name of the property to check.
    • expectedValue: The value that the specified property should hold in each object.
  3. Functionality:
    • It should iterate through each object in the received array.
    • For each object, it must check if the propertyName exists. If it doesn't, the assertion should fail.
    • If the property exists, it must check if its value is strictly equal (===) to the expectedValue. If it's not, the assertion should fail.
    • If all objects in the array satisfy both conditions (property exists and value matches), the assertion should pass.
  4. Error Messages: Provide clear and informative error messages for both failure cases:
    • When an object is missing the specified property.
    • When an object has the property, but its value does not match the expectedValue.
  5. TypeScript Support: The solution should be implemented in TypeScript, leveraging its type-safety features.

Expected Behavior:

  • expect(arrayOfObjects).toHaveEachPropertyWithValue('status', 'active'); should pass if all objects in arrayOfObjects have a property named 'status' with the value 'active'.
  • expect(arrayOfObjects).toHaveEachPropertyWithValue('id', 123); should pass if all objects have an 'id' property with the value 123.
  • The matcher should gracefully handle an empty array, considering it a passing condition as there are no objects that fail the criteria.

Edge Cases:

  • What happens if the input is not an array? The matcher should ideally fail gracefully or throw an appropriate error.
  • What happens if an object in the array is null or undefined?

Examples

Example 1:

const users = [
  { id: 1, name: 'Alice', status: 'active' },
  { id: 2, name: 'Bob', status: 'active' },
  { id: 3, name: 'Charlie', status: 'active' },
];
// expect(users).toHaveEachPropertyWithValue('status', 'active');
// This assertion should PASS.

Explanation: All objects in the users array have a status property, and its value is 'active' for every object.

Example 2:

const products = [
  { id: 'a1', name: 'Laptop', category: 'electronics' },
  { id: 'b2', name: 'Mouse', category: 'electronics' },
  { id: 'c3', name: 'Keyboard', category: 'electronics' },
];
// expect(products).toHaveEachPropertyWithValue('category', 'appliances');
// This assertion should FAIL.

Explanation: While all objects have a category property, none of them have the value 'appliances'.

Example 3 (Property Missing):

const tasks = [
  { id: 101, description: 'Write report', completed: true },
  { id: 102, description: 'Send email' }, // Missing 'completed' property
  { id: 103, description: 'Schedule meeting', completed: true },
];
// expect(tasks).toHaveEachPropertyWithValue('completed', true);
// This assertion should FAIL.

Explanation: The second object in the tasks array is missing the completed property.

Example 4 (Empty Array):

const emptyList: object[] = [];
// expect(emptyList).toHaveEachPropertyWithValue('anyProperty', 'anyValue');
// This assertion should PASS.

Explanation: An empty array contains no elements that violate the condition, so it passes.

Constraints

  • The input array is expected to be an array of objects. The matcher should handle non-object elements or non-array inputs gracefully.
  • Property names are strings.
  • expectedValue can be of any primitive type or a reference type. Strict equality (===) will be used for comparison.
  • Performance is not a primary concern for this challenge, but the solution should be reasonably efficient.

Notes

  • To add custom matchers in Jest, you'll typically use expect.extend().
  • Within your custom matcher function, the this context will provide access to Jest's assertion utilities, such as this.equal(), this.message(), etc.
  • Consider how to handle different data types for expectedValue and the property values.
  • Think about the structure of your custom matcher function and how to return the correct result (a pass/fail object).
  • You'll need to define the types for your custom matcher to integrate seamlessly with TypeScript. This usually involves extending Matchers<void> or Matchers<Promise<void>> on JestMatchers<any>.
Loading editor...
typescript