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.
Jest uses "matchers" to let you test values in different ways. This document will introduce some commonly used matchers. For the full list, see the expect API doc.
With Jest's Object partial matching we can do: test('id should match', () => { const obj = { id: '111', productName: 'Jest Handbook', url: 'https://jesthandbook.com' }; expect(obj). toEqual( expect. objectContaining({ id: '111' }) ); });
A Matcher is the Kotest term for an assertion that performs a specific test.
toBe and compares value: The biggest difference is its behavior with objects. . toEqual looks at key value pairs and tests for equality, rather than making sure they are truly the same object (same reference in memory). And that's about it!
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.
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)
})
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With