- If you are testing code that involves timers,
your tests are gonna run quite slowly,
and that's a bad thing.
Unit tests are meant to be fast.
The idea is that you can run them quickly and often.
If a test is slow,
you're probably gonna be discouraged from runnin' it,
and then your test and your testing rhythm isn't ideal.
You're meant to be running your tests often and quickly.
So that's a problem.
Having slow unit tests is problematic.
Luckily for us, Jest provides an API,
which allows us to mock timers
you can define a fake timer.
So basically replaces timeouts with a no-op,
so your tests run immediately.
They don't have any delay.
So we're gonna see how to do that in this section.
How do you define fake timers in Jest,
so that code that involves timers
don't actually get delayed when you test them?
So if you wanna follow on it's in the Mocking_Timers folder
in the demo folder.
So if you go into "JsTdd Lesson06: Mocking_Timers",
and if you open that folder in the code editor,
it looks like this.
I've got some code here that is quite slow.
I'm going to test it originally using normal test code.
And we're gonna see that the normal tests are slow
because of the timer.
And then what we'll see
is how to define a fake timer using Jest
We place actual timeouts with a mock, or fake,
so that there isn't any actual delay.
So my unit test will run immediately without any hindrance.
So first of all,
we'll have a look at the code that's quite slow
and see how that works.
So here's the code that uses a timer, countdown function.
I defined it in "operations.js" countdown function.
When you invoke it, you pass in a callback.
And the idea is it'll invoke that callback
after a specified delay.
So inside here uses "setTimeout".
It delays for the specified number of milliseconds
like 4,000 milliseconds.
After that time, it'll invoke this lambda,
and that lambda will call your callback
It'll call you back with this message, you know?
"Four seconds countdown complete."
So when I call this function after four seconds, let's say,
it'll call me back with the result,
this kind of string here.
Okay, so that's fine.
Here is a simple test.
First of all, in my "operations.test.js"
I've got a test that uses a real timer,
or what you'll see is that this test
takes four seconds and a bit to complete.
So have a look what's going on.
It uses a real timer, which is the default behavior.
Remember when you've got timeouts involved,
you need to tell Jest when your test has finished,
And the way you do that in your test
is you have a "done" parameter.
This "done" parameter is a callback into Jest,
and the idea was you call that function when you're done,
okay?
And Jest then knows it can move on to the next test,
so it doesn't get any problems.
So first of all, here, I've got a callback function.
This is a callback function.
that's going to be basically invoked for four seconds.
Here is my call to the countdown function.
I'm calling the countdown function.
I'm giving it my callback function as a parameter.
So this pointer here is a pointer to my function.
After four seconds,
it'll call that function back with this message.
"4 seconds countdown complete."
That message will be passed into here.
So this function will be invoked, but after four seconds.
So my test is gonna take four seconds.
I check that the message is complete and direct, correct?
The message that I get back should be
"4 seconds countdown complete"
That's what I'm expecting to get back.
"4 seconds countdown complete"
Great, and then we're done.
The problem is that it'll take four seconds
for this callback to be invoked.
So it'll take four seconds before Jest can progress.
So if I ran that test,
I could run it like this
usin' real timers.
And the key point to note here is the delay it took.
If you ran this for yourself,
it takes four seconds plus a little bit
to actually complete,
okay?
Because the callback function took four seconds
to be called back,
and that's a problem in production code
because your tests are gonna take forever to complete
if you have real timeouts involved.
So rather than having a real timer,
we can get Jest to mock or fake timers.
This is what you do.
You say to Jest "useFakeTimers"
When you call that function, what Jest does
is it basically mocks these functions
"setTimeoout"
"clearTimeout"
"setInterval"
and "clearInterval"
So if your actual code calls any of these functions,
these functions will be replaced by Jest
with no-op operations
Okay?
So in other words,
if your real code called "setTimeout" or "setInterval"
it's not actually gonna instill a timeout or an interval.
Jest will swallow it,
and the delay is avoided.
At some point,
you might want to reinstate the original timer functions.
So if you use fake timers at the beginnin' of your test,
you might want to restore the original timers afterwards.
Okay?
So this, will reinstate the actual "setTimeout,"
"clearTimeout", "setInterval" and "clearInterval"
for the rest of your tests.
So let's see how to do that.
So this is "operationWithFakeTimers.test.js"
In the code. it looks like this.
Okay? So I'm gonna step through the through this example
in a moment,
but I've actually got two tests here.
The first test we're going to look at
is the test that shows how to use fake timers.
So let's have a look at that test function first.
That's what I have here.
So let's step through the code.
So first of all, we tell Jest to use fake timers.
So now whenever we call an operation
that sets a timeout or an interval,
it's not actually gonna take any time.
It's gonna return immediately.
Okay? What I then do, out of interest,
is I spy on the "setTimeout" function, okay?
So you can do that.
"setTimeout" is a global function
It's basically kind of defined on the window object.
So I'm creating a spy that'll say,
"I wanna just verify, you know?
Was that function invoked,
and what parameters was passed into it?"
It's been replaced by Jest,
but my code will still be callin' it.
And I wanna check that it's been called correctly.
So I'm spyin' on this function.
This object here is basically a spy
on the "setTimeout" function,
so I can observe how it's been invoked.
Right?
Okay, now we're ready to actually call our function.
This is the function that I'm testing.
That's the actual countdown function in my real code.
If you remember the countdown function,
it had a callback.
This callback function I've just mocked basically
an empty lambda.
And that function, in my real code,
would've been invoked after four seconds.
Okay? So I've kind of mocked that, and I've passed in four.
So the countdown will set the timeout for four seconds,
but that "setTimeout" has been faked.
So this will not actually take four seconds to complete,
it'll complete immediately.
Right? So that will be fast.
After it's finished, which is, you know?
in a moment's time,
I can verify, "Was the "setTimeout" function invoked
once it should have been?"
When I do a countdown, it should call "setTimeout"
My spy monitors, the "setTimeout" function
Was the "setTimeout" function called once?
Let's make sure it was.
Oh, and also, "What was it called with?"
"setTimeout"
If I just remind you how my countdown function works
That's in here.
My countdown function, it should have called "setTimeout"
with a function
and a value like 4,000.
So we can verify,
we've got a spy that's spying on this function.
We can verify that that function was invoked
with some kind of function
and a value of 4,000 milliseconds,
and that's what I'm doing here.
I'm saying that "setTimeout" function,
"Was it called the last time it was invoked?
"Was it invoked..."
now this syntax here: "expect.any", it's saying,
"Was the "setTimeout" function called
with some kind of function?"
I don't care what function it is.
Some kind of function.
"Was my "setTimeout" function invoked
with some kind of function?"
Yes, it was.
That's verifying that some kind of function
was passed in as the first parameter to "setTimeout"
And by the way, what was the second parameter?
The second parameter should have been 4,000
and verifyin' that this parameter
that was passed into "setTimeout",
the second parameter was 4,000.
Okay? So that was the reason for spying on "setTimeout"
to verify that it was called once,
and to verify that my code called "setTimeout"
with the correct first parameter
and the correct second parameter.
Okay, so I've spied on the "setTimeout" function here.
I'm gonna basically clear the ""setTimeout" function here.
When you clear a spy,
it clears all accrued information by that spy.
It clears the fact that that function's been called once.
It clears the fact that that function was called
with certain parameters.
It resets the spy to have no history,
in case you want to use the spy again in another test.
Okay, so when I run this test here,
the key point is that when I call my countdown,
it doesn't actually wait four seconds to complete.
The "setTimeout" has been faked to return immediately.
So when I run the test...
Oh, and by the way, I forgot to mention this
reinstate real time is at the end.
Okay?
So that will basically put back
the real "setTimeout" function at the end.
When I run my test,
you'll notice that it completes much more quickly.
It didn't wait four seconds to complete.
It basically completed effectively immediately.
Now the key point or one important point,
in my test code
after running each test, I reinstate real timers.
So after that test is finished,
the real "setTimeout" function should be reinstated.
So if I have another test afterwards here
when I run this test,
this test should be using real timers in this case.
So if I ran this test,
that should revert back to give me like a proper delay.
When I call "countdown", it'll call "setTimeout",
it'll be using the real timer.
It reinstates the real timer after each test.
So this should take four seconds
to callback this function here.
Okay, so let's see that then.
When having reinstated real timers,
when I invoke the function,
there should be a real timeout now of four seconds
before this function is invoked.
So let's see if that happens,
and it does.
Okay, so you can mock or fake timers
when they're taking too long,
and you can reinstate them
when you want to experience the full timeout,
if that's important for you.