How I survive testing on NodeJs and Jest 🤒

How I survive testing on NodeJs and Jest 🤒

Common mistakes and errors to look out for when testing with Jest on NodeJs

Subscribe to my newsletter and never miss my upcoming articles

Listen to this article

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 Vs Jest

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.

Tips for testing on NodeJs using Jest

Beware of asynchronous access to data

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 problem

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.

The solution

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);
 });

Always remember your 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();

The problem

This will always be true as it will be testing on the returned Promise object which will not be null.

The solution

Await

const user = await User.findByPk(1); // await 
expect(user).not.toBeNull();

Prefer Debugger to 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.

Mock calls to external APIs or resources

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,
  });
};
 
Share this