- Asynchronous processing is quite commonplace these days
in JavaScript.
So for example, if you have a JavaScript code running
in a web browser,
and you want to make an Ajax call to a rest service
running on the server, it might take a second or two
for the result to come back.
So how do you test that?
Well, one approach is to,
when you call a long-running function,
is to pass into that function a callback,
the idea being that
when the long-running function has completed,
it'll invoke your callback function to say,
here's the result you've been waiting for.
This has quite a common arrangement.
How can you test for that?
Well, we'll see how to test callback functions
in this section.
We'll have a long-running function
that takes a few seconds to complete.
Then it'll invoke our callback, hopefully,
and we'll test that our callback has
in fact been invoked successfully.
So the examples for this section are in that folder,
TestingCallbacks.
And here it is.
We're in Lesson 04, TestingCallbacks.
Okay, if you go into that folder,
the code looks like this.
I have some code that I'm going to test,
and there's some slow code in there.
And then I've got a couple of tests in here.
What we'll do actually, first of all,
we'll see an inappropriate way to run tests,
and then we'll see how to do it properly
and explain what the issue is.
So here is my simple code.
It's slowOperation function.
Notice a few points here.
So first of all, the function receives a callback.
That's a callback function.
In my code here, I call setTimeout.
You know how setTimeout works.
You give it a timeout parameter like two seconds.
After two seconds have elapsed,
it'll do whatever code you specify as the first parameter.
Okay? So after two seconds, it will invoke this code.
It calls me back and it says, thank you for waiting,
and the result you've been waiting for is 42.
That was worth waiting for.
That's a call to my callback function after two seconds.
How can I test that when I call this function,
it will call me back two seconds later
with the correct result?
Right. Well, let's see how to do that.
Here is the first attempt at how to do it,
and I'm gonna explain in a moment.
This is actually not good.
So first of all, I've imported the operations from that,
from my code.
Here's my test function.
I'm testing the slow operation.
This is the incorrect technique.
Okay? So bear that in mind.
Okay. Let's have a look at the code in here, then.
So first of all, I invoke my slowOperation
imported from the ops module.
I call the slowOperation,
and as a parameter, I pass a callback.
TestResult is a callback parameter.
The idea is that it'll call this function back
two seconds later.
This function is here so this function
will be invoked two seconds later,
and it'll grab the result
that's been passed back to me eventually,
and I check that the result is what I expected it to be.
Okay. Now, that seems fine.
If you run the test like this, jest minus T,
this is kind of the descriptor for the function,
test slow operation, comma, incorrect technique.
When you run it, this is what happens.
It seems to pass.
You get a pass indicator.
The test slowOperation, incorrect technique, passed,
but we also get a rather disturbing warning here.
Jest did not exit one second after the test run's completed.
So what basically happened is,
jest called my test function,
and my test function basically terminated immediately,
but the code that it was calling was still running.
So jest finished while my code was still executed.
You know, for two more seconds, my code was still executed.
Jest didn't actually wait for the callback to happen.
It just terminated immediately.
It didn't know that it had to wait
two seconds to check the result.
So in my test function here,
we've just run the test function,
this first test function here.
Jest came in here.
It kind of noticed that function and said,
well, that's very interesting,
but it invoked that.
That function was taking two seconds to complete,
but jest terminated,
and that's the end of that.
Jest didn't realize that it had to wait
for this to also be invoked.
So it's suggesting that
jest finished whilst some asynchronous operations
weren't stopped in time,
and it asks us to consider running this option in our tests.
Okay, so to run our tests again,
with the detectOpenHandles parameter
that will tell me if my tests terminated too quickly
whilst the code was still running.
Well, let's do that then.
We're gonna go to the code window,
and we're going to run jest minus T.
And the prompt is going to be,
this is the name of my function, effectively.
Test slow operation,
operation, comma,
incorrect technique.
Okay.
So that was the name of the function that I want to run.
So when I run it like this, we'll see the message
that was displayed on the screen capture.
It passes, but it says,
but are you sure this is actually correct?
Should you use the detectOpenHandles option
to troubleshoot the issue?
Okay, then let's do that.
So dash dash detect
open handles.
So, is there still some code running when jest finished?
Ah, now it's failed.
No, this is actually the correct outcome,
because my test completed
before the callback had a chance to be invoked.
Jest has detected the following open handle
potentially keeping the test from exiting.
So when my test invoked slowOperation,
slowOperation was gonna take two seconds to finish.
Jest didn't know that it had to wait two seconds,
so it never got a chance to, you know,
check that that actually worked properly.
So when you invoke the slowOperations
that will take longer to execute than your test,
you have to tell jest
wait until I tell you and then complete.
So to do it properly,
when you're invoking a slowOperation
and you have to tell jest to wait until you are ready,
this is what you have to do.
You have to write your test
in a slightly more careful manner.
So this is the correct technique here.
Notice the following points in this example.
My test function call, as well as taking
the name of the test,
it now takes a done parameter.
Done is actually a callback into jest itself, okay?
And the idea is that when we have finished,
when we have been called back,
and when we've verified that the result is correct
and we know that we are ready to finish,
what we do is we call the done function.
Okay? So this is what's gonna happen.
Jest will run that test.
It'll call the slowOperation.
This done parameter jest will know that it has to wait
until we call that function until it finishes.
So we start the slowOperation.
Two seconds later, it'll call us back.
In our test result callback we verify the result is correct,
and then we say, we're done.
So at this point, we are telling jest
we have really finished now,
and it's okay for you to terminate.
So every slow operation, you have to basically take
a parameter here, call it done, invoke that function
when you know that you're ready for jest to terminate.
So I can run that test this time,
and it should give me a correct pass.
It'll take two seconds to complete.
I'll run it in a moment,
but when I run it like this, you can see that it does pass
without any indicators that something was still running.
It did actually wait two seconds before completing.
So I want to actually run that test properly.
So I'm going to just
tweak my handle here,
and I don't need this parameter now,
because there aren't going to be any open handles.
The function will complete properly.
Let's see.
So it'll call my slowOperation.
That takes two seconds to complete.
Jest waits those two seconds and then terminates.
Okay. So it called my function.
Correct technique, no problems at all.
It waited for two seconds,
ran my call back.
My callback asserts that the result was correct.
It was.
And then we can say to jest, you're done now, move on.