Expose matchers in expect.extend
See original GitHub issue🚀 Feature Proposal
Expose existing matchers inside expect.extend
.
Motivation
Sometimes you want the existing functionality of a matcher but you want to it to transform the input before doing so, for instance, to ignore some specific keys of an object.
Writing a custom matcher is extremely verbose and requires importing additional packages to maintain the same quality of the core matchers (diff in messages).
For example, if I want a matcher that performs toEqual
on two objects but ignores a single property on those objects:
expect.extend({
toEqualDesign(recieved, expected, extraMatchers = []) {
const recievedDesign = { ...recieved, change: null };
const expectedDesign = { ...expected, change: null };
const pass = this.equals(recievedDesign, expectedDesign, extraMatchers);
// Duplicated from jest.
// https://github.com/facebook/jest/blob/f3dab7/packages/expect
// /src/matchers.ts#L538-L569
/* eslint-disable */
const matcherName = 'toEqualDesign';
const options = {
comment: 'design equality',
isNot: this.isNot,
promise: this.promise,
};
const message = pass
? () =>
matcherHint(matcherName, undefined, undefined, options) +
'\n\n' +
`Expected: ${printExpected(expectedDesign)}\n` +
`Received: ${printReceived(recievedDesign)}`
: () => {
const difference = diff(expectedDesign, recievedDesign, {
expand: this.expand,
});
return (
matcherHint(matcherName, undefined, undefined, options) +
'\n\n' +
(difference && difference.includes('- Expect')
? `Difference:\n\n${difference}`
: `Expected: ${printExpected(expectedDesign)}\n` +
`Received: ${printReceived(recievedDesign)}`)
);
};
return {
actual: recievedDesign,
expected: expectedDesign,
message,
name: matcherName,
pass,
};
},
});
Example
return expect.extend({
toEqualDesign(recieved, expected, ...args) {
const recievedDesign = { ...recieved, change: null };
const expectedDesign = { ...expected, change: null };
return {
...this.matchers.toEqual(recievedDesign, expectedDesign, ...args)
name: 'toEqualDesign',
};
},
});
and then:
expect(a).toEqualDesign(b);
expect(a).not.toEqualDesign(b);
Pitch
I am aware this has been asked for before:
The response was to use expect.extend
and I do not think it considers these cases where using expect.extend
as it stands is not only massively inconvenient upfront for such a simple comparison but creates longer term debt having to maintain the matcher, whereas leveraging the return value of a core matcher allows your matcher to benefit from the continued maintenance of it in the jest core, e.g., if it gets improved messages or the already very verbose matcher return API changes.
This proposal is to enable the ability to write matchers that don’t want to introduce new matching behaviour but want to transform their inputs before matching.
Other alternatives include:
expectToEqualDesign(a, b) {
expect({ ...a, change: null }).toEqual({ ...b, change: null });
}
You then have to handle not
yourself by either making separate functions or flagging it:
expectToEqualDesign(a, b, { not: false } = {}) {
let expectation = expect({ ...a, change: null });
if (not) {
expectation = expectation.not;
}
expectation.toEqual({ ...b, change: null });
}
Which will work, but now requires you to know an entirely different syntax because of a slight difference to the matcher.
Issue Analytics
- State:
- Created 3 years ago
- Reactions:20
- Comments:5
Top GitHub Comments
I ended up here from #2547, and @SimenB you’d asked for use cases in that one (…admittedly ~3 years ago 😄), but similar to @georeith I want to make a custom matcher that a) accepts args, b) does some pre-processing, and then c) hands off to an existing matcher, in my case
toMatchObject
to leverage it’s great out-of-the-box formatting/diffing/etc capabilities.Basically, in our project, the
actual
instance that is passed to myexpect(actual).toMatchObject({ ... })
has ugly implementation details that I want to clean up (almost like a.toJSON
to get it to be “just data”) for thetoMatchObject
.In my case I’m using a
require
hack for now:With @georeith 's proposal, the
require
hack would go away and this could become:@bpinto hm, no, I haven’t tried to re-use
objectContaining
yet, so I’m not sure how/if it would be different.