DEV Community

Cover image for How I survive testing on NodeJs and Jest 🤒
Victor Anuebunwa
Victor Anuebunwa

Posted on • Originally published at log.victoranuebunwa.com

How I survive testing on NodeJs and Jest 🤒

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.

Giphy

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

Giphy

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);
 });
Enter fullscreen mode Exit fullscreen mode

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);
 });
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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,
  });
};
Enter fullscreen mode Exit fullscreen mode

Top comments (2)

Collapse
 
adam_cyclones profile image
Adam Crockett 🌀

Looking at a glance, is PHP unit more declaratively wrote and therefore easier to follow if you are used to that mindset?

Collapse
 
avonnadozie profile image
Victor Anuebunwa • Edited

Well, I don't think any of them is easier to follow than the other.
In terms of style of writing, they are similar except when you have to deal with asynchronous operations in JavaScript. That's where the difficulty comes in.