How to test Express.js with Jest and Supertest
I recently changed from Mocha
, Chai
to Jest
. Pretty lovely experiences. It took a while to figure out the right way to test an express route, but when it works, it works like a charm. I share my experiences here, hope it helps. I love jest assertion function more than Chai
, seems more concise to me.
1. Install
You need to install babel-cli
, babel-preset-env
, jest
, supertest
and superagent
.
1 | npm install --save-dev babel-cli babel-preset-env jest supertest superagent |
2. Separate your app
and server
The reason behind this is that it won’t listen to the port after testing.
1 | //app.js |
This is your server.js
:
1 | //server.js |
Then you can start your server via node server.js
, and you can import that app.js
for testing.
3. Use done
to notify that it ends
Jest test will end when it hits the last line of the test function, so you need to use a done()
to make it right.
1 | const request = require("supertest"); |
But that done
is not a must, the following one is more neat.
4. Promise way
1 | const request = require("supertest"); |
Two Things to be noticed here:
- That
return
is crucial, otherwise your tests will get stuck. - No need to pass
done
to your test.
5. Async, await way to test
It’s good to see that my beloved async
and await
from .net
has a place in javascript world too.
1 | const request = require("supertest"); |
Does the imperative programming style and synchronous looking make you feel happy? :D Two things also be noticed here.
- Use
async
to the function before you want to useawait
- You need the
babel-preset-env
package to use this. - You don’t need to use
babel-preset-env
if you use Node8 or above because it’s supported.
6. Why not the Supertest way
Supertest way is still available. You just need to return
the statement and remember not use .end()
and the end.
Thanks Adam Beres-Deak for the hint!
A working example is as the following:
1 | const request = require("supertest"); |
Notice that without that return
, the test will always pass.
7. About the Database connection
You need to handle the database connection through the tests. In my case, I used this pattern:
1 | describe('Test the addLike method', () => { |
Here I use mongoDB. I will connect at the beginning of this test suite, and disconnect at the end. And the mongoDB
just a wrapper class.
You may consider using beforeEach
and afterEach
to manage the database connection to prevent race condition. More explanation below.
1 | module.exports = { |
Notice that done
? It is a variable used to indicate that an async operation is finished. Jest
will give that as a parameter for that afterAll
.
8 How to run the tests
You might want to use jest --runInBand
to run the tests depending on how you structure your tests. Otherwise, multiple tests access same collection will cause random failing for your tests.
But thanks to one of the readers, Melroy van den Berg
, I think you don’t need --runInBand
if you open and close database connection with beforeEach
and afterEach
rather than the beforeAll
and afterAll
. Not tested, but it should be the root cause of the problem that I was encountering previously!
Why? Because when you try to test CRUD against the same record for same collection with the same database connection, of course you will encounter race condition. Even though this is something that won’t happen in production time.
And when you simply open new connection for each test, then this race condition problem will be solved naturally in the database layer.
And why do we need to worry about how many database connection we are opening during test time?! :D
But if your machine can’t handle it. Then feel free to maintain database connection in a test suite basis. Nothing wrong with that, your tests are still isolated if you done it right. And manage database connection is a test basis won’t promise the isolation of tests, it’s a different problem.
9. End
That’s all, hope it help. :)
Thanks for reading!
Follow me (albertgao) on twitter, if you want to hear more about my interesting ideas.