- In the last lesson, we saw how you can mock
an existing function in jest.
You call jest.fn, and it gives you back a mock function.
If you want to, you can mock all the functions in module,
as we've seen already, you call jest.mock.
You specify the name of your module,
the name of the file,
and it will mock every function in that file.
So effectively, it replaces every function in this module
with a mock version.
It avoids calling the original function.
Now, sometimes, rather than replacing a function,
you want to spy on, or intercept calls
to an existing function.
So to do that, you can call jest.spyOn
So import a model that contains functions
upon which you want to spy, call jest.spyOn,
specify the module that you just imported,
and specify the function that you want to intercept.
This would give you back a spy, which intercepts calls
to the function that you've just specified.
So the spy here detects
and records information about calls to that function.
But the original function is still called as normal.
So there is a different philosophy here,
when you say mock, or jest.fn,
its replacing a call to divisional function,
whereas when you say jest.spyOn,
the ultimate function will still be invoked, but you get
to record information about how it was invoked,
it's interceptor or spy.
So we gonna look at an example in the Mocking_SpyOn folder.
So if you want to follow on, this is where it's located.
If you're gonna do less than six,
we're in the Mocking_SpyOn folder, here it is.
And if you open that up in a code editor,
this is what it looks like.
As I'm gonna show you in a moment,
I have high level operations in operations.js,
and functions in there called lower level functions
in their messaging.js.
And when I call a function on here,
I'm going to spy on how it calls functions in here.
So let's see the scenario pictorially.
So in my high level module, operations.js,
I'm gonna have high level functions.
I mean, they're not that high level,
add, subtract, multiply, and divide.
When I call the add function, or the subtract,
or the multiply or the divide,
those functions will invoke lower level functions
in messaging.js to output
or to generate informational messages,
or warning messages, or error messages.
So the functions on the module
on the left will call functions on the module on the right.
When I call the add function in my code, for example,
it will call one of these functions over here.
And what you can do is you can define a spy
to intercept those calls.
So the spies kind of sit in the middle.
They intercept calls to the info method,
or the warn method, or the error method.
And the motivation for doing this
is to see was the info method called?
Was it called with the right parameter?
Was the warn method not called?
You can check, or you can verify how these methods here
interact with these methods here to make sure
that these methods get called as expected.
So these are my low level messages.
These are the messages
on the right hand side, messaging.js.
These are the messages that I'm going to spy on,
or the functions I'm gonna spy on.
I have an info function, it takes a message
it builds up a string.
It gets the current time stamp.
Oh, by the way, I've got helper module called Take datetime,
which basically returns the current date and time.
You can have a look at that if you like,
it's here, datetime.js.
Okay, so it has a now string function,
it just returns the current date
and time as an ISO standard string,
a standard string format, year, year, year, year,
month, month, day, day, hours, minutes,
and seconds and milliseconds.
Anyway, so that's basically what that function does.
So that's not particularly significant.
So when we call the info function with a message,
it will return the time stamp
and info followed by the message, and warning followed
by the message, and error followed by the message.
So these are the low level functions,
but I'm going to call for my high level.
I want to spy on how these functions are invoked.
So my high level code looks like this.
I import those lower level functions
that I just showed you.
The add function, it calls message.info
to generate an informational message,
you know, within the add function with parameter A
and parameter B, that should be turned a string,
say an info, which we display on console.
And the subtract method also displays
an informational message.
And the multiply does as well.
The divide method is a bit more interesting
because if you divide by zero,
that's potentially problematic.
So if the denominator is zero, I output a warning message.
So this function will either call the warn method
or the info method.
If the denominator is zero, it'll gimme a warning.
If the denominator isn't zero,
it'll just open an information message.
So that's relatively interesting, when I call this function,
I can spy on whether it called that one, or that one,
and that's what I'm going to do in my tests.
So just before we look at the tests,
I'll just show you the actual code,
just to confirm it's as I've explained.
So just to reiterate then my low level functions here.
My function that returns an informational message,
my function that returns a warning message,
and my function that returns an error message.
Those are the functions I'm gonna spy upon.
I call those functions
for my high level code in operations.js.
Okay, and that code as I just explained as well.
It either caused message info,
or message one, or potentially message error
if I had some problematic situation.
So now we're gonna have a look at the test code
to see how when I call this function, how can I spy
on how it interacts with these lower level functions?
How can I ensure in my test that when I call this function,
it has in fact called
the correct lower level method properly.
So let's have a look at the test code.
Here's my first step.
I'm going to set up a spy on those three functions.
So at the top of my test harness, I spy, well,
first of all, I import the messaging module, messaging JS.
I spy on the info function,
and the warn function, and the error function.
Those are the actual names of the functions,
and you get back a spy.
So remember what's going on here.
We had the warn, the function, and the error function.
Oh, and the info function, I forgot about that one.
Those are the three functions we've got.
And what I'm setting up here,
what I'm setting up here, here, and here,
I'm setting up a spy that kind of sit here.
And it will monitor and detect any calls that are made
to the info, or the warn, or the error function.
These spies here are these objects here.
These are my spies which will detect calls
to those individual functions.
When you set up a spy, it accumulates information.
Every time you call the SpyOn function,
it has an internal counter to say,
"Oh, you've called that function again."
So what I'm gonna do, at the end of each test,
I'm going to clear all mocks.
When you say, "Clear all mocks," it basically wipes out
any accumulated information in these spies.
Like, it sets the call count back to zero.
And that's important, when you're doing unit testing,
it's important that you kind of start
with a blank canvas every time.
So just to point to those notes,
we first of all imported the messaging module.
Obviously, we need to do that
if we're gonna call those functions.
Define spies on those functions.
And after each test, clear all information accrued
by those spies.
So basically, each spy, reset the count to zero
in each case ready for the next test to take place.
So that code sits at the top of my test harness.
If you wanted to take a look,
here it is operations.testjs.
This is the code where I set up my spies.
I could have called it info spy, warn spy, error spy.
Oh, and there, after each test,
we'll clear all accrued information ready to go again.
I've actually got two tests.
I've got one test there, one test there,
let's have a look at those tests now.
So the first test, I'm gonna call the divide function.
So well, as I just discussed, probably about a couple
of minutes ago, you know, when you call the divide function,
what the divide function does,
it depends on whether the numerator, sorry,
the denominator was zero or non-zero.
If the denominator was non-zero,
then the divide function will say, "That's great,
thank you very much for that."
And it caused the info function to get back
an informational message, "I've got a spy sitting here
that'll detect that call."
However, if I passed in the denominator of zero,
then instead of calling the info function,
it'll call the warn function.
And I've got a spy that detects that as well.
Oh, and also, just for good measure,
I've also got a spy that's ready to intercept any calls
to the error function if those took place.
So what I do when I call the divide function,
the divide function will proceed as normal, it will call...
Well, if I pass in a denominator of 20,
now that's a good condition, it'll call the info function.
And afterwards, and this is the purpose of spies,
after you called the function of interest,
you can then check the info spy to say,
"Has that been called,
and has it been called with that information?"
Because let me just remind you
how the divide function worked.
Here's my divide function.
So if the denominator isn't zero,
it should have called the info function with that parameter.
So we're gonna check the spy now to say,
"Well, did it call the info function
with that parameter, with that string?"
And that's what my check does,
it's verifying that having done
that, the info mock function that well, you know,
that spy tells me that the info function was called,
and it was called with these parameters in divide,
with the parameters value A and B at 10 and 20.
We can also verify,
and this is another powerful aspect of spies,
you can also check that a certain function wasn't called.
You can say, "Okay, let's check
that the info function was called.
Let's check that the warning function was not called."
It's important that you didn't call the warn function,
because it's not a warning situation.
And it's important
that the error function wasn't called as well.
So the value of spies, as I can tell you,
how your function interacted with lower level functions,
did it call them, and with what parameters?
And did it not call that function,
and did it not call that function?
Okay, so just to recap,
I invoke the function under test, my divide function.
It will call, well in this situation,
it'll call the info function.
So I verify that it has actually called the info function
with that parameter specified here.
And I also verify that it didn't call the warn function
or the error function.
So that's my first test,
I can run that test like this.
That's the name of the test,
spying on a function, example one.
When I run that test, I ran it earlier,
it displays this.
Well, it does actually output a console message.
It displays an informational message here,
but also, you noticed
that it has passed, everything in there worked.
It has interacted correctly
with the low level functions.
The second test, same idea,
but this time we'll go down the warning route
for the divide function.
So this time, I'm gonna call the divide function,
but I'm gonna pass in zero.
And according to the way the divide function works,
if the denominator is zero, then in that case,
we should, the divide function,
I mean, I've obviously written the code here,
so I know it's gonna happen.
But if I was verifying, I can verify that in that situation,
it should call warn with that parameter.
In divide with the value of A and the value of B.
So I can verify that it does call the warn function,
and that it doesn't call the others.
So here we go, let's call the function and the test,
let's verify that it called the warn function
because it's detected a warning situation.
Let's also verify that it didn't call the info function,
or the mock function.
So all we need to do now is to run this test.
So I can run that test like this, example two,
and it works as well when it called the warning function.
It did actually output a console message with a warning,
and it verifies that it did interact successfully
with those lower level functions.
So the purpose of spying is rather than like,
checking the result, you can check,
how does your function call other functions?
Does it call them properly?
And does it call them with the right parameters?