How I survive testing on NodeJs and Jest 🤒
Tags: node, javascript, testing
Published: 2021-08-31 15:57:28

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](https://media.giphy.com/media/l3978rwTcbblCh224/giphy.gif)


## 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](https://media.giphy.com/media/1n833bZxdzKzaErLe9/giphy.gif)

### 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
```JavaScript
// 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.
```JavaScript
// 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:

```JavaScript
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
```JavaScript
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:
```JavaScript
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,
  });
};
```