Heads up! To view this whole video, sign in with your Courses account or enroll in your free 7-day trial. Sign In Enroll
Preview
Start a free Courses trial
to watch this video
Mocha allows us to say that a test spec or test suite is asynchronous. In this video, you'll learn how to pass an argument to the internal function of a 'describe' or 'it' block to tell Mocha to wait on running expectations until we specifically say so.
Resources
Video review
- Mocha allows us to say that a test spec or test suite is "asynchronous"
- Passing an argument to the internal function of a
describe()
orit()
block will tell Mocha to wait on running our expectations until we specifically say so - Passing the
done
argument to our test spec tells Mocha that it’s supposed to wait for our instructions before checking our expectations. - Mocha will wait for
done()
to fire before checking the expectations
Testing asynchronous functions
can be a unique challenge.
0:00
Our battleship game doesn't have
any asynchronous elements but
0:03
we might want it to.
0:07
For example, If it were played across
an Internet connection on two computers,
0:08
one player would have to wait for
the other's turn.
0:12
If we wanted to save
our game somewhere and
0:15
start again later we'd need to connect to
a database that stored our game state.
0:17
Those processes would probably
be synchronous for efficiency.
0:22
Mocha allows us to say that a test back or
test suite is asynchronous.
0:25
So passing an argument to the internal
function of a describe or it block,
0:30
we'll tell Mocha to wait on running our
expectations until we specifically say so.
0:35
Let me show you how it works.
0:40
I'll create the function inside the main
GAME INSTANCE FUNCTIONS suite right below
0:41
the takeTurn suite.
0:46
Now, in this function we'd normally have
to write some code to communicate with
0:55
a remote database.
0:59
But instead, I'll simulate that
idea with a setTimeout function.
1:00
So setTimeout will perform whatever
function we describe after a specified
1:17
number of milliseconds.
1:21
If our game really communicated
with a database the seTimeout
1:23
function would be replaced with something
that sent a request to our database.
1:27
So I'll set the function to run after one
second has passed, or, 1000 milliseconds.
1:31
So after one second the database function
would finish and perform some callback,
1:38
like telling the player that
the game was saved successfully.
1:42
So let's pass callback as
a parameter of saveSame.
1:47
Then we'll call callback
inside setTimeout.
1:52
All right, so now we can create
the test suite for saveGame.
1:57
So right below the saveGame function,
as always,
2:01
we'll create a new test suite by typing,
describe.
2:04
And we're going to name this one,
saveGame.
2:08
And then I'll open my first spec for
this function inside the suite.
2:17
So we'll say,
it('should update save status').
2:20
And now I have a space to
write some expectations
2:37
about how saveGame should work.
2:40
So to test synchronous functions
like the ones we've written
2:41
before now in the course, I would run
the function inside of my spec and
2:44
then write an expectation about
something that changed as a result.
2:49
So to use that strategy in this case, I
might try running saveGame with a callback
2:52
and writing an expectation at the end of
the callback to capture the new results.
2:57
So let's do that, at the top of the spec
I'll save the string, game not saved,
3:04
in a new variable named status.
3:09
Then I want saveGame to change the status
to, game saved, when it finishes.
3:20
Again, in reality, that would mean
the database responded successfully.
3:26
But in this example it's just
going to wait one second and
3:30
then use my callback function
to update the status favorable.
3:33
So inside saveSame we'll say,
status = game saved.
3:37
Finally, I'll try expecting
the status to equal game
3:46
saved after I've called saveGame.
3:51
So right below saveGame, we'll say
expect(status).to.equal('game saved').
3:54
So now if I bring up the console and
run my tests,
4:08
We can see that the test
reveals a failure.
4:17
The expectation runs before the saveGame
function is called because saveGame is
4:21
waiting 1000 milliseconds to fire
the callback that updates status.
4:26
So that means status will
still equal game not saved
4:31
until an entire second has passed,
which we can see it's
4:35
much longer than our test suite takes
to reach the expect(status) line.
4:38
So to fix this problem, I need to move my
expectation inside the saveGame callback.
4:49
So that it only happens after saveGame
has finished updating status.
4:56
So now if I go back to the console and
run my test at this point
5:05
It looks like the tests pass.
5:13
But that's kind of tricky.
5:15
Mocha is just running through the test
function before saveGame finishes.
5:17
And then reports that the test spec
passes because they didn't see any
5:22
expectations written.
5:25
So for example, even if I write
a definitely failing expectation into
5:33
the saveGame callback,
like expect(true).to.be.false;.
5:37
Mocha still passes our tests.
5:50
So we need a way to tell Mocha to wait
until saveGame has finished running
5:53
in order to check our expectations.
5:58
To do that, I can pass a special argument
to our test specs callback function.
6:04
By convention we call the argument done so
that we know the argument is signaling
6:10
Mocha that our test code is ready to be
run after some a synchronous operation.
6:15
We normally just use the test spec
callback to contain our test code and
6:20
expectations, but
by passing it an argument
6:24
we signal to Mocha that we want
this test to run asynchronously.
6:26
So now I'll delete this
example expectation.
6:31
Then run, npm test.
6:37
And now we see that the test
gives me a new error that says,
6:42
saveGame should update save status:.
6:46
Then the error says,
timeout of 2000ms exceeded.
6:48
Ensure the done() callback is
being called in this test.
6:52
So it looks like the error is asking for
me to call the done() callback.
6:56
Passing the done argument to our test spec
tells Mocha that it's supposed to wait for
7:00
instructions before
checking our expectations.
7:05
So we passed the done argument but
7:08
we haven't used it to tell Mocha
when our code is ready to be tested.
7:11
So, in the saveGame callback,
7:16
I'll add done() after
the changes I expect to occur.
7:18
And now Mocha will wait for
7:22
done to fire before
checking the expectations.
7:25
So when we run the tests now.
7:30
Great, everything works.
7:36
And notice that there's a short
pause while the tests are running.
7:38
That's Mocha waiting one second for
7:42
the setTimeout function to run
before checking the expectations.
7:44
Writing asynchronous code and
testing it correctly is a big topic.
7:55
Usually you'll write mocks and stubs for
your database or AJAX calls because
7:59
those are big external systems that you
don't wanna mix into your test unit.
8:03
Just know that Mocha gives you
a way to deal with these problems.
8:08
So, you might need to research the best
way to capture the test results you want
8:11
when you encounter them.
8:15
So congratulations getting started
with JavaScript unit testing.
8:17
Good unit testing takes practice.
8:21
And there's a lot more you can learn and
do to improve the quality of your test and
8:23
also to make writing tests easier.
8:27
If you've never written any tests this
testing first approach is different from
8:30
what you're used to.
8:34
And at first it can feel
like a lot more work.
8:35
As you get better writing unit tests
really will improve your code.
8:38
It will also save you lots
of time fixing bugs and
8:42
explaining things to other developers.
8:45
So dig into Mocha and chai and
8:47
let us know in the community when
you find useful tips and tricks.
8:49
Good luck and happy testing.
8:53
You need to sign up for Treehouse in order to download course files.
Sign up