Become a Master of JavaScript Testing
Tags: javascript, testing, tutorial, jest
Published: 2022-07-08 18:06:36

What a Spaceship and JavaScript have in common? Both already reached space.

![Photo by NASA on Unsplash](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x3zej1nqcezii17knurq.jpg)
 
The Crew Dragon Spaceship from SpaceX uses JavaScript in the main cockpit panels[[1]](https://os-system.com/blog/javascript-in-space-spacex-devs-have-shared-crewdragons-tech-stack/). It's super cool to see where the language has come and what can be achieved with it.

Just like rockets, spaceships, and many others, critical and non-critical projects, require a lot of testing before production launch. Otherwise, a “_KaBuM! Effect_” could happen, and unless it is a firework, it won't make anyone happy.

In any case, **testing is not complicated**, and even if most of us are not building things that can explode, treat them as if they are of equal importance. Testing makes error detection easier and can also save a lot of time. It can be tricky at first, but with practice and experience, it becomes an ally, you just need to **make it part of your daily work**.

Before getting started, we need to take a look and understand how things work under the hood. This article will cover the basics of testing using JavaScript, including:

* [Testing Fundamentals](#testing-fundamentals)
* [Testing with Jest](#testing-with-jest)
* [Mocking Fundamentals](#mocking-fundamentals)
* [Static Code Analysis](#static-code-analysis-in-javascript)

## Testing Fundamentals

One of the most common phrases in software development is: “**whattaf*ck…**”, some say that the quality of the code can be measured by the FPS (f*cks per second) heard during the development process. $h!t happens, and fixing it can be simple, but if it is a little more complicated, it can take days, weeks, and even months to solve it. Thus, the idea of creating an automated test is to try to catch as many errors as possible in our code before they happened.

Imagine that you are building a spaceship, and this spaceship requires a calculator module and if it fails it can explode. You aim to make sure the results are always correct. Then, you start creating the first method of this module.

```javascript
// calculator.js
export const sum = (a, b) => a + b;
```

<sup>[Show code in action](https://stackblitz.com/edit/node-tqn3be?file=src/calculator.test.js)</sup>

To test this code, you have to check the result of your function to validate your assumption.

```javascript
// calculator.test.js
import { sum } from './calculator.js';

const expected = 4;
const result = sum(2, 2);

if (result !== expected) {
  throw new Error(`KaBuM! It Exploded!`, { cause: `${result} is not equal to ${expected}` });
}
```

<sup>[Show code in action](https://stackblitz.com/edit/node-tqn3be?file=src/calculator.test.js)</sup>

In the example, you run and test to check if the result is what you expected. Although this implementation works, it cannot be reused. To simplify the testing process, extract the logic into a new method, that way it can now be used for more cases.

```javascript
// testing.js
export const expect = value => ({
  toEqual(expected) {
    if (value !== expected) {
      throw new Error(`KaBuM! It Exploded!`, { cause: `${value} is not equal to ${expected}` });
    }
  }
});
```

<sup>[Show code in action](https://stackblitz.com/edit/node-tqn3be?file=src/calculator.test.js)</sup>

Now, update our previous code.

```javascript
// calculator.test.js
import { sum } from './calculator.js';
import { expect } from './testing.js';

expect(sum(2, 2)).toEqual(4);
expect(sum(2, 'a')).toEqual(NaN); // Error
```

<sup>[Show code in action](https://stackblitz.com/edit/node-tqn3be?file=src/calculator.test.js)</sup>

Much better! However, there is no description showing what is being tested, and if you start adding more tests and one fail, the remaining tests will not run, so let's fix it by encapsulating this code inside a try/catch:

```javascript
// testing.js
export const test = (description, fn) => {
  try {
    fn();
    console.log(`✓ ${description}`);
  } catch (error) {
    console.error(`✕ ${description}`);
    console.error(error);
  }
};
// ...
```

<sup>[Show code in action](https://stackblitz.com/edit/node-tqn3be?file=src/calculator.test.js)</sup>

Now, use our new function inside our test code.

```javascript
// calculator.test.js
import { sum } from './calculator.js';
import { expect, test } from './testing.js';

test('sum numbers', () => expect(sum(2, 2)).toEqual(4));
```

<sup>[Show code in action](https://stackblitz.com/edit/node-tqn3be?file=src/calculator.test.js)</sup>

Let's open the terminal and run our test:

```shell 
$ npx babel-node calculator.test.js
```

![screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/esctueb526y5g6v8fc5s.png)

In case of an error in the code, you will see the following error message.

```javascript
// calculator.test.js
...
test('sum numbers', () => expect(sum(1, 2)).toEqual(4));
```

<sup>[Show code in action](https://stackblitz.com/edit/node-tqn3be?file=src/calculator.test.js)</sup>

![screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qi6ymwwbin903llycyc2.png)

Congratulations! You have now created a simple JavaScript Testing Framework. The good news is that there are already some great tools for testing automation. The most famous is [Jest](https://jestjs.io/), and you can make your test compatible with it by just removing one line of code and run it:

```javascript
// calculator.test.js
import { sum } from './calculator.js';
test('sum numbers', () => expect(sum(2, 2)).toEqual(4));
```

<sup>[Show code in action](https://stackblitz.com/edit/node-whhysy?file=src/calculator.test.js)</sup>

In the terminal, run the command:

```shell 
$ npx jest calculator.test.js
```

![screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/01p6ixcrzgz4se0tnc4g.png)

There is more than just that. Let's move on and learn more about how to test an application, even without running the code (Yep, this is possible in JavaScript).

## Testing with Jest

[Jest](https://jestjs.io/) is a delightful JavaScript Testing Framework with a focus on simplicity. It has already built in most of the features you expect from a testing framework: A great set of exceptions, code coverage, mocking, runs fast, good documentation, and an incredible community around it.

First things first, you need to install Jest

```shell 
$ npm install --save-dev jest
```

Thereafter, update the **package.json** file to run it:

```json
...
"scripts": {
  "test": "jest"
},
...
```

<sup>[Show code in action](https://stackblitz.com/edit/node-pnqamh?file=package.json)</sup>

`npm run test` or also manually: `npx jest`

If you like, you can also run `jest --init` to create a configuration file.

### Comparing Values

Let's go ahead, now you are going to create a weapon module for your spaceship. Start creating a simple test example:

```javascript
describe('Weapon Module', () => {
  test('a simple test', () => {
    expect(2 + 2).toBe(4);
  });
  // ...
});
```

<sup>[Show code in action](https://stackblitz.com/edit/node-pnqamh?file=src/weapon.test.js)</sup>

After running [Jest](https://jestjs.io/), this is the result:

![screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ubk4x1vp2offzcicdhwa.png)

The primary comparison methods are `toBe` and `toEqual`, `toBe` uses **_===_** to check strict equality, while `toEqual` makes a deep comparison of the properties of the values using **_Object.is_**.


```javascript
// ...
const weapon = { type: 'laser' };
test('check object with toEqual', () => {
  expect(weapon).toEqual({ type: 'laser' });
});
test('check object with toBe', () => {
  expect(weapon).toBe({ type: 'laser' });
});
// ...
```

<sup>[Show code in action](https://stackblitz.com/edit/node-pnqamh?file=src/weapon.test.js)</sup>

Running this test, you will get the following result:

![screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w6rql8mn5ziru0f5t7cp.png)

It is up to you to decide which one fits better in your test case, but if you are starting with testing, using the `toEqual` method will probably be the best alternative.

### Comparing Strings

#### Regular Expression

Jest does have support for comparing strings. Besides, the regular `toEqual` regex can also be used for comparison. All you need is to call the `toMatch` method and pass in the regex string.

```javascript
const text = 'hello world';
test('string comparison', () => {
  expect(text).toMatch(/hello/);
});
```

<sup>[Show code in action](https://stackblitz.com/edit/node-pnqamh?file=src/weapon.test.js)</sup>

#### Length

It's also possible to compare the length between two strings using `toHaveLength`.

```javascript
expect('abc').toHaveLength(3);
```

<sup>[Show code in action](https://stackblitz.com/edit/node-pnqamh?file=src/weapon.test.js)</sup>

It works with an array as well.

```javascript
expect([1, 2, 3]).toHaveLength(3);
```

<sup>[Show code in action](https://stackblitz.com/edit/node-pnqamh?file=src/weapon.test.js)</sup>

### Comparing Numbers

Besides the basic comparison methods, you can easily compare numbers in your tests by utilizing the following methods:

- _toBeGreaterThanOrEqual_
- _toBeGreaterThan_
- _toBeLessThanOrEqual_
- _toBeLessThan_

In the following example, you can use a loop to check if the result is less than 10.

```javascript
test('loop less than', () => {
  for (let i = 1; i < 10; i++) {
    expect(i).toBeLessThan(10);
  }
});
```

<sup>[Show code in action](https://stackblitz.com/edit/node-pnqamh?file=src/weapon.test.js)</sup>

When changing the value from 10 to 5, you will receive the following error message.

![screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b85f2wg142wpa2zgd4wx.png)

### Comparing Arrays

The `toContain` method is used for array comparison, which checks if the values are included in the list.

```javascript
test('check an array', () => {
  const weapons = ['phaser', 'laser', 'plasma cannon', 'photon torpedo'];
  expect(weapons).toContain('laser');
});
```

<sup>[Show code in action](https://stackblitz.com/edit/node-pnqamh?file=src/weapon.test.js)</sup>

### Comparing Dynamic Values

In a situation where you don't have an exact value, but you know the type of the object, so you can use the `expect.any` method.

#### Primitive Values

For primitive values like string, number, and booleans, you can use:

- expect.any(String)
- expect.any(Number)
- expect.any(Boolean)

```javascript
test('check dynamic string', () => {
  expect('disruptor').toEqual(expect.any(String));
  expect(1).toEqual(expect.any(Number));
  expect(false).toEqual(expect.any(Boolean));
});
```

<sup>[Show code in action](https://stackblitz.com/edit/node-pnqamh?file=src/weapon.test.js)</sup>

#### Objects

You can check an Object with `objectContaining` to see if an object contains some properties inside. In this case, you don't need to match the same properties from the object you want to evaluate.

```javascript
test('check dynamic object', () => {
  const weapon = { type: 'laser', damage: 100, range: 10, available: false };
  expect(weapon).toEqual(
    expect.objectContaining({
      damage: expect.any(Number),
      type: expect.any(String),
      available: expect.any(Boolean),
    })
  );
});
```

<sup>[Show code in action](https://stackblitz.com/edit/node-pnqamh?file=src/weapon.test.js)</sup>

#### Arrays

It's also possible to use `arrayContaining` to check the values, and you can even combine them with all previous checks.

```javascript
test('check dynamic array', () => {
  const weapons = [
    { type: 'phaser', damage: 150, range: 15, speed: 'fast' },
    { type: 'photon cannon', damage: 10000, range: 100, speed: 'slow' },
  ];
  expect(weapons).toEqual(
    expect.arrayContaining([
      expect.objectContaining({
        type: expect.any(String),
        damage: expect.any(Number),
        range: expect.any(Number),
        speed: expect.any(String),
      }),
    ])
  );
});
```

<sup>[Show code in action](https://stackblitz.com/edit/node-pnqamh?file=src/weapon.test.js)</sup>

### Asynchronous Code

There are multiple ways to handle asynchronous code, depending on your needs.

**Callback**

The easiest way to handle callback is to use a single done argument when calling the callback function. For example,

```javascript
test('test callback', done => {
  initBattleMode((data) => {
    try {
      expect(data).toEqual({ ready: true });
      done();
    } catch (error) {
      done(error);
    }
  });
});
```

<sup>[Show code in action](https://stackblitz.com/edit/node-pnqamh?file=src/weapon.test.js)</sup>

**Promise**

Asynchronous code with a promise is a lot easier, as all you need to do is to return the promise. Have a look at the modified example:

```javascript
test('test promise', () => {
  return initBattleMode().then((data) => {
    expect(data).toEqual({ ready: true });
  });
});
```

<sup>[Show code in action](https://stackblitz.com/edit/node-pnqamh?file=src/weapon.test.js)</sup>

**Async/await**

On the other hand, using async/await is a lot more straightforward. So let's reuse the previous example and modify it to use async/await instead.

```javascript
test('test async', async () => {
  const data = await initBattleMode();
  expect(data).toEqual({ ready: true });
});
```

<sup>[Show code in action](https://stackblitz.com/edit/node-pnqamh?file=src/weapon.test.js)</sup>

This is just a taste. For a complete list of matches, take a look at the [reference docs](https://jestjs.io/en/expect).

## Mocking Fundamentals

Occasionally, when doing our tests, you can't rely on real data because it's slow, private, or for other reasons.

> Mocking allows you to intercept or erase the actual implementation of a function, capture calls (and the parameters passed in those calls), and enable test-time configuration of returned values.

One way to deal with this situation is to mock (faking) your data. [Jest](https://jestjs.io/) has already built-in some great tools with data mocking. It uses a custom resolver for imports in your tests, making it simple to mock any object outside your test's scope. In addition, you can use mocked imports with the rich Mock Functions API to spy on function calls with readable test syntax.

Let's focus on two types of mocks using [Jest](https://jestjs.io/), the mock function, and the mock module.

### Mock Functions

To mock a function, you just need to declare the method as a jest function: `jest.fn()`, with that, you can start our evaluation. Here is a quick example:

```javascript
// engine.test.js
// ...
describe('Rocket Engine', () => {
  const cb = jest.fn();
  beforeEach(() => {
    cb.mockReset();
  });
  test('check callback response', () => {
    cb.mockImplementationOnce(() => 2).mockImplementation(() => 1);
    expect([1, 2, 3].map(cb)).toEqual([2, 1, 1]);
    expect(cb).toHaveBeenCalledTimes(3);
  });
  // ...
});
```

<sup>[Show code in action](https://stackblitz.com/edit/node-tjhulf?file=src/engine.test.js)</sup>

First, declare your mock method, and then you defined the first and the default outputs. Next, check if the output matches our expected result. Thereafter, check if the method was called the amount of time expected and if the parameters were correct.

If you want to learn more, refer to the [reference docs](https://jestjs.io/en/mock-functions)

### Mock Modules

Mocking a module works similarly to mocking a function, but instead of applying it to a function, you have to intercept a module import. 

Back to the spaceship idea, create a `startEngine` method that receives a callback function as a parameter and does an HTTP call to an API server. In this case, you have to mock the `unfetch` module.

```javascript
// engine.js
import fetch from 'unfetch';

export const startEngine = async (callback) => {
  const res = await fetch('https://api.space.com/rocket/engine/start');
  const json = await res?.json();
  if (json) {
    if (callback) {
      callback(json);
    }
    return json;
  }
  return undefined;
};

```

<sup>[Show code in action](https://stackblitz.com/edit/node-tjhulf?file=src/engine.test.js)</sup>

Now, declare the values you want to the mock module.

```javascript
// engine.test.js
// ...
jest.mock('unfetch', () => () => ({
  json: () =>
    Promise.resolve({
      status: 'ready',
      fuel: '100%',
      power: 100,
      sensors: [{ type: 'temp', value: 50, active: true }],
    }),
}));
// ...
```

<sup>[Show code in action](https://stackblitz.com/edit/node-tjhulf?file=src/engine.test.js)</sup>

There is also an alternative way to declare your module as an _esModule_.

```javascript
// engine.test.js
// ...
jest.mock('unfetch', () => ({
  __esModule: true,
  default: () => ({
    json: () =>
      Promise.resolve({
        status: 'ready',
        fuel: '100%',
        power: 100,
        sensors: [{ type: 'temp', value: 50, active: true }],
      }),
  }),
}));
// ...
```

<sup>[Show code in action](https://stackblitz.com/edit/node-tjhulf?file=src/engine.test.js)</sup>

The first parameter is the **modules name**, and the second one is the **factory method**. You now have configured it to make the output values always be the same.

```javascript
// engine.test.js
describe('Rocket Engine', () => {
  const cb = jest.fn();

  beforeEach(() => {
    cb.mockReset();
  });

  test('check engine response', async () => {
    const data = await startEngine();

    expect(data).toMatchObject({
      power: 100,
      fuel: '100%',
      status: 'ready',
      sensors: [{ type: 'temp', value: 50, active: true }],
    });
  });
});
```

<sup>[Show code in action](https://stackblitz.com/edit/node-tjhulf?file=src/engine.test.js)</sup>

Using the previous mock, you can check the results. When executing, the output should be the same as declared before.

```shell 
$ npx jest mock.test.js
```

![screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nj2i3nodr2yeut09ch88.png)

For a complete list of mock functions, see the [reference docs](https://jestjs.io/en/mock-function-api).

## Static Code Analysis in JavaScript

There are a ton of ways your program can break. JavaScript is a loosely typed language. The most common bugs are typos and incorrect types, like the wrong variable name or the sum operation of two strings instead of integers.

### What is “Static Analysis”?

So, what does mean “static analysis” of code? The answer is:

> Predicting defects in code _without_ running it.

Since JavaScript is a scripting language, instead of the compiler running the code analysis, you need to use **formatters** and **linters** to get the job done.

### Formatters

Formatters are tools that can fix any style inconsistencies it finds automatically. For this purpose, tools like [Prettier](https://prettier.io/) or [StandardJS](https://standardjs.com/) can do the job. There are a couple of options to configure it to best match your criteria, and it can be integrated with the most popular editors and IDEs.

To show you how does it work, here is an example of an unformatted code:

```jsx
// unformatted_code.jsx
function HelloWorld({greeting = "hello", greeted = '"World"', silent = false, onMouseOver,}) {

  if(!greeting){return null};

  // TODO: Don't use random in render
  let num = Math.floor (Math.random() * 1E+7).toString().replace(/\.\d+/ig, "")

  return <div className='HelloWorld' title={`You are visitor number ${ num }`} onMouseOver={onMouseOver}>

    <strong>{ greeting.slice( 0, 1 ).toUpperCase() + greeting.slice(1).toLowerCase() }</strong>
    {greeting.endsWith(",") ? " " : <span style={{color: '\grey'}}>", "</span> }
    <em>
      { greeted }
    </em>
    { (silent)
      ? "."
      : "!"}

  </div>;

}
```

After using prettier, here is the result:
```bash
$ npx prettier --write unformatted_code.jsx
```

```jsx
// formatted_code.jsx
function HelloWorld({ greeting = 'hello', greeted = '"World"', silent = false, onMouseOver }) {
  if (!greeting) {
    return null;
  }

  // TODO: Don't use random in render
  let num = Math.floor(Math.random() * 1e7)
    .toString()
    .replace(/\.\d+/gi, '');

  return (
    <div className="HelloWorld" title={`You are visitor number ${num}`} onMouseOver={onMouseOver}>
      <strong>{greeting.slice(0, 1).toUpperCase() + greeting.slice(1).toLowerCase()}</strong>
      {greeting.endsWith(',') ? ' ' : <span style={{ color: 'grey' }}>", "</span>}
      <em>{greeted}</em>
      {silent ? '.' : '!'}
    </div>
  );
}
```

As you can see, the main benefit is that you don't need to worry about these minor inconsistencies anymore. It does that for you automatically.

> Remember that you write code for the machine to interpret, but for humans to read.
 
The clearer and more consistent your code is, the easier it is to understand what is happening.

### Linters

Code linting is a way to increase code quality. It analyzes the code and reports a list of potential code quality concerns. Currently, the most used tool for that is [ESLint](https://eslint.org/).

> ESLint is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code

Let's check our example:

```javascript
// unconsistent_code.js
function sayHello(name) {
  alert('Hello ' + name);
}

name = 'John Doe';
sayHello(name)
```

<sup>[Show code in action](https://stackblitz.com/edit/node-59r4nn?file=src/unconsistent_code.js)</sup>

To use ESLint, you need to install it first. Then, open the terminal and type on your project folder.

```shell 
$ npx eslint --init
```

Now, you can run [ESLint](https://eslint.org/) on any file or directory, like in this example.

```shell 
$ npx eslint unconsistent_code.js
```

![screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/heste96ophqzupwlmwxg.png)

The linter shows where are the errors in our code, based on a set of [rules](https://eslint.org/rules/) in the `eslinrc.{js,json,yaml}` file. You can also add, remove, or change any [rules](https://eslint.org/rules/). For example, let's add a rule to check if we are missing a semicolon.

```javascript
// eslint.js
...
rules: {
  semi: ['error', 'always']
},
...
```

When executed again, the result will show you an error with the new rule.

![screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q2xkchbygfvu8iyox4sl.png)

This was a simple example, but the bigger the project, the more it makes sense to use it and catch many trivial errors that could take some time if done manually.

There are some sets of rules that can be extended, so you won't need to set them one-by-one like the _recommended_ rules (`" extends": "eslint:recommended" `), and others made by the community like the [Airbnb](https://github.com/airbnb/javascript) or [Standard](https://standardjs.com/) that you can include into your project.

For a complete list of rules, refer to the [reference docs](https://eslint.org/rules/).

## Conclusion

In this article, you've learned more about how to start adding tests to your program and understanding the foundations of testing in JavaScript, Jest, Mocking, and Static Code Analysis and that's only the beginning. But don't worry about that, the most important thing is to add your tests while you are coding, saving you from numerous problems in the future.