Implementing expect.extend in Jest for Custom Matchers
Jest's expect.extend allows you to add custom matchers to the expect assertion library, making your tests more readable and expressive. This challenge asks you to implement a basic expect.extend functionality that allows users to register custom matchers with descriptive names and logic. This is crucial for writing tests that are tailored to your specific application's domain and data structures.
Problem Description
You need to implement a function extendExpect that takes a Jest expect object and a matcher factory function as input. The matcher factory function should accept a single argument (the value being asserted) and return an object with pass and message properties. The pass property should be a boolean indicating whether the assertion passed, and the message property should be a string providing a descriptive message for both passing and failing assertions. The extendExpect function should then add this custom matcher to the expect object.
Key Requirements:
- The
extendExpectfunction must accept a Jestexpectobject and a matcher factory function. - The matcher factory function must return an object with
passandmessageproperties. - The
extendExpectfunction must add the custom matcher to theexpectobject, allowing it to be used in assertions. - The added matcher should be accessible via a string name provided to the matcher factory.
- The
messageproperty of the returned object should be a function that accepts the expected and received values and returns a string.
Expected Behavior:
When extendExpect is called with a valid expect object and matcher factory, the custom matcher should be available on the expect object. Assertions using the custom matcher should behave as expected, with appropriate passing and failing messages.
Edge Cases to Consider:
- What happens if the matcher factory function doesn't return an object with
passandmessageproperties? (Consider throwing an error or logging a warning). - What happens if the
expectobject is invalid (e.g., not a Jestexpectobject)? (Consider throwing an error). - How to handle multiple matchers with the same name? (The last registered matcher should overwrite previous ones).
Examples
Example 1:
Input:
expect: Jest's expect object
matcherFactory: (value) => ({ pass: value > 5, message: (received, expected) => `Expected ${received} to be greater than ${expected}` })
matcherName: "toBeGreaterThan5"
Output:
expect(7).toBeGreaterThan5() // Passes
expect(3).toBeGreaterThan5() // Fails with message "Expected 3 to be greater than 5"
Example 2:
Input:
expect: Jest's expect object
matcherFactory: (value) => ({ pass: value === "hello", message: (received, expected) => `Expected "${received}" to be "${expected}"` })
matcherName: "toBeHello"
Output:
expect("hello").toBeHello() // Passes
expect("world").toBeHello() // Fails with message "Expected "world" to be "hello""
Example 3: (Edge Case - Invalid Matcher Factory)
Input:
expect: Jest's expect object
matcherFactory: (value) => "not an object"
matcherName: "invalidMatcher"
Output:
Error: Matcher factory must return an object with 'pass' and 'message' properties.
Constraints
- The
extendExpectfunction should be compatible with standard Jest versions. - The matcher factory function should be synchronous.
- The
messagefunction should accept two arguments: the received value and the expected value. - The
extendExpectfunction should not modify the originalexpectobject directly; it should add the matcher as a new property. - The matcher name should be a string.
Notes
- You'll need to understand how Jest's
expectobject works internally to implement this effectively. Consider how Jest handles custom matchers. - Focus on creating a robust and well-documented solution.
- Error handling is important – consider what should happen if the input is invalid.
- Think about how to make the
extendExpectfunction reusable and easy to use. - The
messagefunction is crucial for providing informative error messages. Make sure it's well-formatted and includes the relevant values.