- So far in the examples we've been looking at,
we've been using the standard Jest matchers,
like toBe and toThrow, greater than and less than,
and that's fine.
But it's also possible to define
higher level custom matchers,
which give you a more semantic test.
You can encapsulate complicated business logic
in a custom matcher,
and then reuse that custom marcher many times.
It gives you like a higher level of vocabulary
for doing your tests.
So you could write a custom matcher
to be valid UK post code,
or to be valid international dialing code.
You know, semantically richer
makes it easier to write high level tests.
So in this section,
we'll see how to write a simple Jest marcher,
that just takes a single value, single parameter
and checks that it's okay.
So to define a custom test matcher for Jest,
you, first of all you have to call the expect function,
the expect extend function.
And into that function, you pass an object.
Another object contains your custom matchers.
Each matcher will receive at least one value,
the value that you're testing,
possibly other parameters as well.
But initially we'll just pass in one parameter
into our first example.
And what you have to return is an object.
So it's quite strict.
You return an object that has a pass property
which is either true or false,
and then a message which will be displayed by Jest,
if you know, if it fails.
So that's the error message that the user will see
if the test fails.
Okay, right.
So the examples, I've got some examples.
If you go into the custom Jest matchers folder,
the examples in there.
Okay, so in JsTdd lesson two, custom Jest matchers.
If you open that folder in the text editor,
it looks like this.
And what I've done is I've put my custom matcher
into a file called setupTests.js.
It actually doesn't matter what you call this file name.
So I've just called it that, setupTests.js.
And as you can see here,
I've called the expect extend function.
You give it an object.
And in that object, you define your custom matchers.
So in this example, I've got two custom matchers.
One that checks if a number is a valid exam mark,
you know, zero to a hundred.
We'll have a look at that one in this section.
And then in the next section,
we'll have a look at a more complicated custom matcher
that takes multiple parameters.
Okay, where it's a bit more complicated.
The test is a bit more involved.
So first of all, our simple Jest matcher,
it'll take a parameter, hopefully a number.
And it'll tell us if it's a valid exam mark.
Okay, so here's the code.
It, first of all, checks the incoming parameter.
It's gotta be a number.
If it isn't a number, fail immediately, throw an error.
We were expecting the value to be a number.
Okay, assuming it is a number.
If the number is between zero and a hundred
then we return true, it is a valid exam mark.
Now the message is kind of the opposite condition.
We've just established that the number is valid,
a valid exam mark.
But if the user was checking to see if it wasn't valid,
then it fails that test, okay?
So we've just, we know that the number's valid
but if the user was testing for it not to be valid,
then that's the error message to display,
the user was expecting the number not to be a valid mark,
but it actually is.
So this is kind of the opposite condition
of what we just established.
That's what we know it is
but that's what the user was trying to test for.
And that's the error message they would get.
However, if the number isn't between zero and a hundred,
then we return false,
an object that returns the false property.
And so it's not a valid mark.
If the user was hoping it was,
then this is the error message they'll get back.
We were expecting it to be a valid number
and it isn't, is it?
So that's our custom tests in setupTest.js.
Like I said, there's nothing special about this file name.
You have to tell Jest to look in this file
to find the custom matcher, okay?
So there we go.
Here's my custom file.
It contains my custom matchers or however many I've got.
In order for Jest to know to look in this file,
you have a configuration file.
A special config file for Jest, Jest.config.Jest.
This file name does matter.
And in there you just basically export
the names of the files
where you use to find your custom matchers.
So you say exports, you give it,
you set the setupFilesAfterEnv property.
And then you give it an array of file names, possibly many,
where you've defined your custom matchers, okay?
And that ensures that when Jest get started,
it'll load these custom matchers into its system.
So you can then use those custom matchers in your tests.
Okay, so that's the next thing for us to do
is to actually write some tests
that use this custom matcher.
So to use that Jest matcher, you'd say this,
you'd say, I'm expecting some number to be an exam mark.
And, so what Jest will do,
is it'll take that parameter or that value,
and it'll automatically pass that parameter
into your custom matcher, okay?
You don't pass it in yourself, you just write it like this.
Jest will automatically take that parameter
and pass it into your matcher.
And then your matcher returns, you know, true or false.
You can check if it isn't a valid exam mark like this.
So again, Jest will pass in value into your matcher.
And it knows this time,
you're hoping that it isn't a valid exam mark, okay?
So it kind of knows whether you're looking
for a positive outcome or negative outcome
based on whether it sees the not, keyword here.
Right, so what I've done is I've defined three suites
that show how to use my matcher
when a good value is expected.
How to use the matcher when a bad value is expected,
and how to use the matcher
if the user accidentally passes in a non-numeric exam mark,
like widdle.
So let's take a look at that.
And what we'll do, is we'll,
well we could run all the tests at once, like this.
npm run test -t "Using toBeExamMark".
I think what I'll do is I'll run the tests a few times
as we go through the code
just to show the progressive experience.
So in my example test here, the first test suite
I've got in there a few functions.
The first function, I'm expecting the value to be good
and my custom matcher should pass when it sees a good mark.
So I'm given in, I'm expecting that to be valid.
But what I'm actually doing here is I'm testing my matcher.
I'm testing that my matcher
successfully determines that to be valid,
and that one and that one.
Okay, so all of these should work fine.
Okay, good.
Now have a look at this.
I've got some here, I'm expecting a good value.
That's what I'm hoping for,
but my custom matcher should fail
if we don't get a valid mark.
Okay, so imagine this was some matcher.
We thought it was valid
and we were hoping it was gonna be valid.
If I passed that value into the matcher,
my matcher should fail, it,
I was hoping it was gonna be good, but it should fail.
It should realize that that is actually not valid.
Okay, and so if I uncomment that statement,
then this test should fail.
My custom matcher should realize
that this is not a valid exam mark.
So let's try running the tests.
npm run test -- -t.
And we're going to give it this, "Using toBeExamMark".
Okay, so that select the string there
will pull in my three first suites.
Okay.
toBeExamMark, toBeExamMark, toBeExamMark.
Okay, and I think I probably need a capital U here.
Okay, so it'll find this test suite and it run those tests,
and those ones and those ones.
I might run it again in a moment,
just with some other parameters.
So remember what we're expecting,
I'm expecting these tests to all succeed
because the marks are valid,
and the matcher should say, "Yeah, they're good."
I'm expecting this one.
I'm expecting my custom matcher to fail this test
because the value that the user passed in
isn't valid actually,
and my custom matcher should detect
the fact that it's not valid.
Success.
Now, when you get errors
the messages tend to be quite verbose.
In fact, there's a few failures here.
It was expecting a good value,
but it failed if it didn't get it.
That error there is because of this test here.
Okay, and what I should do as well,
I won't bother running this but what I should do,
is I should also run this test.
Okay, that should also fail because 101 isn't valid.
My custom matcher should realize that it isn't valid
and basically fail.
Right, I'll just comment that back up again.
Let's have a look at the second suite.
In the second suite.
Oh, uncomment one of these following tests.
I'm expecting a bad value, pass if we get a bad value, okay?
So in this case here,
I'm saying I'm expecting -1 not to be a valid exam mark.
Okay, so I'm expecting my custom matcher
will actually pass.
It'll say, okay, so that -1,
I'm expecting it not to be valid mark.
My custom matcher should confirm the fact
that that really isn't, is not a valid mark.
And likewise with this one, if I pass that in
I'm expecting my custom matcher to also realize
and be successful indicating
that that is not a valid exam mark.
What about this one here?
I'm expecting a bad value.
Okay, I've got not, here, to say,
I don't think this is valid.
So whatever parameter in here I pass in,
I'm expecting that parameter not to be a mark, a valid mark.
Okay, so generally this would be a number here nnn.
So I'm expecting it not to be a valid mark
but the thing is, it is a valid mark.
So again, my custom matcher should detect that.
According to the test here,
I was expecting it not to be valid.
It fails the test, okay, because it is not invalid.
It's actually valid.
As you have to think about Boolean logic here.
So these tests should work.
Okay, because those are not valid,
and it detects that they're not valid.
This one should fail because it is valid
but I was checking if it was invalid.
That kinda makes sense to me.
So I'm gonna save that and I'm gonna run my tests again.
So in this case, I'm expecting that test to pass
and we're gonna be expecting that test to fail.
Let's see what we get.
Okay, so in my second suite here,
the first test successfully detected invalid numbers.
And this one here, the fact that the test failed
is actually the correct outcome here, isn't it?
My custom matcher realized that the expectation here
has not been met, okay?
I was expecting it not to be valid.
Well, that expectation hasn't been met,
my custom matcher is intelligent enough to find that.
And finally, have a look at this.
So what I'll do is I'll just comment that test out again,
so we don't keep on getting errors on that one.
In my custom matcher, the first thing it did
was to check if the parameter was a number.
Okay, so if the user accidentally passed in a non-number,
my custom matcher should realize,
it should say, "Hang on, this isn't a valid number."
So that should cause my test to fail.
So let's see if that does indeed cause my test to fail.
We're almost done with this example now.
I think the most difficult thing here
is trying to figure out the logic
of whether it's meant to be true or meant to be false.
So you have to understand what test the user's trying to do.
Are they looking for a good outcome or not a good outcome?
And the test has to be written
in the correct kind of Boolean logic.
Okay, so fail if the value is non-numeric.
That actually is the correct outcome.
Okay, when you give it non-numeric input,
okay, that should fail.
Now what about the next one?
This one should also fail.
Okay, because it doesn't even get us far
but let's see what it does.
Should we actually see what happens when we run this?
Okay, so this last one here,
it should fail if it's non-numeric.
The, to be a valid exam mark
only makes sense if it's a number.
If you pass in a string
then it doesn't even bother check in the range,
it just fails immediately.
So whether you're expecting it to be an exam mark
or not an exam mark, the correct behavior is, I think,
if you pass in a string, that doesn't make sense
so it should fail in any case.
Okay, so that did fail and I would expect it to fail.
I would only expect it to pass if it was a number.
So a bit tricky.
Like I said, the key point
is you have to understand the Boolean logic.
In your custom matcher,
you have to know is the user looking for a positive outcome
or a negative outcome.
And then you kind of return the appropriate error message
from your custom matcher.