Coming from a PHP background and with PHPUnit testing, I started my journey into writing tests on NodeJs with some expectations.
For most, I was disappointed but for some, I was blown away. I guess this is a feeling you have to get used to with JavaScript.
PHPUnit provides you with more test functions to work with, has better error tracing, and is easier to debug.
However, testing on NodeJs is faster than testing with PHPUnit.
Correction: testing on NodeJs is way faster than testing with PHPUnit, because Jest runs your tests in parallel, and in the world of CI/CD, this means something very important. Fast deployment time! 🙌🏽
This is great, however, working with tests that run in parallel comes with its own challenges.
Tests running in parallel means that you will have multiple tests making requests to the database at the same time.
Expect inconsistency from tests like this.
// Get User Test
test('get user', async () => {
const response = await request
.get('/v1/user/1')
.set('Authorization', `Bearer sample-token`)
.send();
expect(response.status).toBe(200);
});
// Delete User Test
test('delete user', async () => {
const response = await request
.delete('/v1/user/1')
.set('Authorization', `Bearer sample-token`)
.send();
expect(response.status).toBe(200);
});
The "Get user" test is going to be inconsistent depending on which of the tests runs first. If the "Delete User" test runs first, the "Get User" test will fail by the time it runs because the user will no longer exist.
Ensure that each test works with its own unique data.
// Get User Test
test('get user', async () => {
// Create a new user
const user = User.create({name: "Sample user 1"});
// Get the user
const response = await request
.get(`/v1/user/${user.id}`)
.set('Authorization', `Bearer sample-token`)
.send();
expect(response.status).toBe(200);
});
// Delete User Test
test('delete user', async () => {
// Create a new user
const user = User.create({name: "Sample user 2"});
// Delete the user
const response = await request
.delete(`/v1/user/${user.id}`)
.set('Authorization', `Bearer sample-token`)
.send();
expect(response.status).toBe(200);
});
Promises
Always remember to await functions that return a promise.
Obvious right? I will bet you still forgot one some minutes ago.
On a serious note, these kinds of errors in tests can mess up your week and are difficult to detect. For example:
const user = User.findByPk(1); // no await
expect(user).not.toBeNull();
This will always be true as it will be testing on the returned Promise
object which will not be null.
Await
const user = await User.findByPk(1); // await
expect(user).not.toBeNull();
console.log
Debugger adds more flare to error tracing, get used to it.
Debuggers allow you to literally go into the function and see what happens step by step and view the real content of each variable at any point, while console.log
only shows you the string representation of the variable you log which could be hiding that extra piece of information you need to figure the bug out.
Additionally, console.log
codes can easily find their way to production and you find yourself unknowingly logging sensitive information which could be dangerous.
This is more of a general tip when testing with any framework.
For most, your test should focus on testing the functions and features of your app, not the functionality or output of an external application.
Avoid consuming external resources during tests as this could introduce inconsistencies to your code when those requests fail and also increase the time your tests take to run. It's best practice to mock these resources or API responses instead.
Example:
const getSignedUrl = (key, bucket = null) => {
if (process.env.NODE_ENV === 'test') {
return `https://s3.eu-west-2.amazonaws.com/sample/${key}`;
}
return s3.getSignedUrl('getObject', {
Bucket: bucket,
Key: key,
Expires: 60,
});
};
Also published on https://log.victoranuebunwa.com/how-i-survive-testing-on-nodejs-and-jest.