Custom matcher in jest
Solution 1
There are two different kinds of methods that are related to expect
. When you call expect(value)
you get an object with matchers methods that you can use for various assertions (e.g. toBe(value)
, toMatchSnapshot()
). The other kind of methods are directly on expect
, which are basically helper methods (expect.extend(matchers)
is one of them).
With expect.extend(matchers)
you add the first kind of method. That means it's not available directly on expect
, hence the error you got. You need to call it as follows:
expect(string).stringMatchingOrNull(regexp);
But when you call this you'll get another error.
TypeError: expect(...).stringMatching is not a function
This time you're trying to use use expect.stringMatching(regexp)
as a matcher, but it is one of the helper methods on expect
, which gives you a pseudo value that will be accepted as any string value that would match the regular expression. This allows you to use it like this:
expect(received).toEqual(expect.stringMatching(argument));
// ^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// string acts as a string
This assertion will only throw when it fails, that means when it's successful the function continues and nothing will be returned (undefined
) and Jest will complain that you must return an object with pass
and an optional message
.
Unexpected return from a matcher function.
Matcher functions should return an object in the following format:
{message?: string | function, pass: boolean}
'undefined' was returned
One last thing you need to consider, is using .not
before the matcher. When .not
is used, you also need to use .not
in the assertion you make inside your custom matcher, otherwise it will fail incorrectly, when it should pass. Luckily, this is very simple as you have access to this.isNot
.
expect.extend({
stringMatchingOrNull(received, regexp) {
if (received === null) {
return {
pass: true,
message: () => 'String expected to be not null.'
};
}
// `this.isNot` indicates whether the assertion was inverted with `.not`
// which needs to be respected, otherwise it fails incorrectly.
if (this.isNot) {
expect(received).not.toEqual(expect.stringMatching(regexp));
} else {
expect(received).toEqual(expect.stringMatching(regexp));
}
// This point is reached when the above assertion was successful.
// The test should therefore always pass, that means it needs to be
// `true` when used normally, and `false` when `.not` was used.
return { pass: !this.isNot }
}
});
Note that the message
is only shown when the assertion did not yield the correct result, so the last return
does not need a message since it will always pass. The error messages can only occur above. You can see all possible test cases and the resulting error messages by running this example on repl.it.
Solution 2
I wrote this hack to use any of the .to...
functions inside .toEqual
, including custom functions added with expect.extend
.
class SatisfiesMatcher {
constructor(matcher, ...matcherArgs) {
this.matcher = matcher
this.matcherArgs = matcherArgs
}
asymmetricMatch(other) {
expect(other)[this.matcher](...this.matcherArgs)
return true
}
}
expect.expect = (...args) => new SatisfiesMatcher(...args)
...
expect(anObject).toEqual({
aSmallNumber: expect.expect('toBeLessThanOrEqual', 42)
})
Related videos on Youtube
rober710
Updated on September 16, 2022Comments
-
rober710 over 1 year
I'm trying to create a custom matcher in Jest similar to stringMatching but that accepts null values. However, the docs don't show how to reuse an existing matcher. So far, I've got something like this:
expect.extend({ stringMatchingOrNull(received, argument) { if (received === null) { return { pass: true, message: () => 'String expected to be null.' }; } expect(received).stringMatching(argument); } });
I'm not sure this is the correct way to do it because I'm not returning anything when I call the stringMatching matcher (this was suggested here). When I try to use this matcher, I get:
expect.stringMatchingOrNull is not a function
, even if this is declared in the same test case:expect(player).toMatchObject({ playerName: expect.any(String), rank: expect.stringMatchingOrNull(/^[AD]$/i) [...] });
Please, can somebody help me showing the correct way to do it?
I'm running the tests with Jest 20.0.4 and Node.js 7.8.0.
-
5422m4n over 4 yearsjest-bot.github.io/jest/docs/expect.html#expectextendmatchers explains it quite good
-
-
rober710 over 6 yearsOops! Thanks, @Michael, I forgot to write my intended usage of this matcher. I wanted to use it as a helper method inside the
toMatchObject
matcher. I've edited my question with the snippet that caused the error. From your answer, I assume usingexpect.extend
is not the right way to do it. Are there any docs related to how to write helper functions forexpect
directly? -
Michael Jungo over 6 yearsI don't think there is an official way to create such a helper method that is providing a pseudo value. You could try to define one yourself like
StringMatching
. Either way, a value beingnull
is an indication of a bug. If you explicitly set it tonull
you should have separate tests that cover these scenarios to make sure that it is only evernull
when you expect it. Testing for a value ornull
sounds like you squash multiple tests together. -
quezak over 3 years@MichaelJungo how can I add my custom matcher to the second kind too, the helper methods called directly on expect? expect.extend() seems to only add it to the first kind, but the matchers available in the
jest-extended
package work directly on expect too, for exampleexpect(o).toEqual({ aNumber: expect.toBeWithin(1, 3) })
. -
David Harkness over 2 years@MichaelJungo Do you need to check
this.isNot
in the first check fornull
to reverse it or when returningpass: true
? -
Zyncho almost 2 yearsplease, add how to implement custom matcher class // #script.test.js expect(result).myCustomMatcherclass().method2(); // #myCustomMatcherclass.js class myCustomMatcherclass { constructor() {...} method2(){...} method2(){...} }