Setup jest from scratch in a vanilla js project.
Tags: javascript, webdev, tutorial, testing
Published: 2022-05-13 11:59:04

From time to time I see myself setting up a web project from scratch. Something super-easy if you use [`create-react-app`](https://create-react-app.dev/) (if you're using React)... but not that _smooth_ in vanilla.

> ➡️ This occasion we needed at work a **vanilla js project**. 

A co-worker was setting up [webpack](https://webpack.js.org/) and asked me to enable [jest](https://jestjs.io/).

And I was like, "_sure this should take 10 mins at most_".

<img src="https://www.dentaltix.com/sites/default/files/estiramientos-brazos.png" />

It took longer.

***

### 1. Install jest

First step is obv. 

```bash
$ yarn add -D jest
```

```diff
  ...
  "html-webpack-plugin": "^5.5.0",
+ "jest": "^28.0.3",
  "postcss-loader": "^6.2.1",
  ...
```

### 2. Base Config

Next, call the `jest CLI` tool which will create the base config:

```bash
 $» jest --init

The following questions will help Jest to create a suitable configuration for your project

✔ Would you like to use Jest when running "test" script in "package.json"? … yes
✔ Would you like to use Typescript for the configuration file? … no
✔ Choose the test environment that will be used for testing › jsdom (browser-like)
✔ Do you want Jest to add coverage reports? … no
✔ Which provider should be used to instrument code for coverage? › babel
✔ Automatically clear mock calls, instances and results before every test? … no
```

> ➡️ Note: Here we've taken one relevant decision. **[test environment](https://jestjs.io/docs/configuration#testenvironment-string)** is `jsdom` since we're building a web app, we want a browser-like environment.

### [3]. Optional Step

We try to reduce _dot-files-pollution_ at root folder. Basically, we gather every tool-related config file and move them to `config/`. 

```bash
$» mv jest.config.js config/
```

This implies a subtle tuning at `jest.config.js`

```diff
...
- roots: ['src']
+ roots: ['../src'],
...
```

Plus considering the new path at `package.json`

```diff
...
- "test": "jest"
+ "test": "jest --config=config/jest.config.js"
...
```

### 4. Configure Jest

Jest runs over node. [From the docs](https://jestjs.io/docs/configuration#transform-objectstring-pathtotransformer--pathtotransformer-object):

> Jest runs the code of your project as JavaScript, hence a transformer is needed if you use some syntax not supported by Node out of the box.

Since we're playing with browser-things, we need [`babel-jest`](https://www.npmjs.com/package/babel-jest).

```bash
$» yarn add -D babel-jest @babel/preset-env @babel/core
```

```diff
  ...
+ "@babel/core": "^7.17.9",
+ "@babel/preset-env": "^7.16.11",
+ "babel-jest": "^28.0.3",
  ...
```

Plus these lines at `jest.config.js`

```diff
+ transform: {
+   '\\.[jt]sx?$': 'babel-jest',
+ },
```

And (if you didn't have one already) a brand new `config/babel.config.json`

```json
{
    "env": {
        "test": {
            "presets": [
                "@babel/preset-env"
            ],
        }
    }
}
```

### 5. Try the thing with a real test

One of the things I hate the most from tutorials is that they use _fake-hello-world_ examples.

Our **real world** test is:

```js
import axios from 'axios';
import api from './api';

jest.mock('axios');

afterEach(() => {
    jest.clearAllMocks();
});

test('getLayoutData() [200]', async () => {
    axios.get.mockResolvedValue({ status: 200, data: { layout: 'hello world' } });

    const id = 42
    const layoutData = await api.getLayoutData(id, { someQueryParam: true });

    expect(layoutData).toEqual({ layout: 'hello world' });
    expect(axios.get).toHaveBeenCalledTimes(1);
    expect(axios.get).toHaveBeenCalledWith('https://fake-url.api.test/v1/layouts/42', {
        headers: {
            'x-api-key': '==API KEY TEST==',
        },
        params: {
            someQueryParam: true,
        },
    });
});
```

> I bet you have one of those at your **real world problems**

### 6. ENV vars

Our code relies on two env vars; these two values: `'https://fake-url.api.test'` and `'==API KEY TEST=='`.

Let's add those:

1. At `config/jest.config.js`

```diff
+setupFiles: ['./jest-env-vars.js']
``` 

2. `config/jest-env-vars.js`

```js
process.env.API_URL = 'https://fake-url.api.test';
process.env.API_KEY = '==API KEY TEST==';
```

***

AND... **It fails** 🤔

***

### 7. Enable `async` `await`

Straight to the point what it's happening is that we need yet another babel plugin: [`@babel/plugin-transform-runtime`](https://www.npmjs.com/package/@babel/plugin-transform-runtime) 

Enable the plugin at `babel.config.js`

```diff
{
    "env": {
        "test": {
            "presets": [
                "@babel/preset-env"
            ],
+            "plugins": [
+                "@babel/plugin-transform-runtime"
+            ]
        }
    }
}
```

### 8. Final touches 💅

At some point we'll rendering some component, and testing those you want the handy methods `toBeInTheDocument()`, `toBeVisible()` from [Testing Library](https://testing-library.com/docs/ecosystem-jest-dom/)

```bash
$» yarn add -D @testing-library/jest-dom
```

1. At `config/jest.config.js`

```diff
+ setupFilesAfterEnv: ['./setup-tests.js'],
``` 

2. `config/setup-tests.js`

```js
import '@testing-library/jest-dom';
```

***

## Summary

With _just_ **7 dev-dependencies**,

- `@babel/core`
- `@babel/preset-env`
- `@babel/plugin-transform-runtime`
- `@testing-library/jest-dom`
- `babel-jest`
- `jest`
- `jest-environment-jsdom`

And _just_ **4 config files**,

- babel.config.json
- jest-env-vars.js
- jest.config.js
- setup-tests.js

we got `$» yarn test` working 😅

--
> Cover image <a href="https://storyset.com/people">People illustrations by Storyset</a>

Thanks for reading 💚.