Node.js (Express) with TypeScript, Eslint, Jest, Prettier and Husky - Part 3
Tags: jest, supertest, node, express
Published: 2020-08-27 07:12:56

Let's start testing!

## Why you should do testing?

Before we continue with our setup I wanted to say some words regarding why you should do testing.

> Yes testing is time consuming, and from time to time also hard but testing will save your ass in the long run. 

Different testing techniques/tools exists and you should not (dont need to) cover all of them, also if use use multiple techniques/tools try to find a balance to not repeat your tests.

Finding the balance on what to test and what not may be hard, specially if you work with large teams, so my suggestion is to set up some rules that everyone follows, here are some rules we at [Ornio AS](https://ornio.no) try to embrace when it comes to testing Node.js applications:
- All utility and service functions should be followed with a test, this should cover most of our functionality since we use Repository (service) pattern.
- Validations (we use Joi) should also be tested.
- Test Error handling.
- Test Middlewares using Supertest.
- Test critical Controllers using Supertest.

❓ What about you? What do you test in your Node.js applications?

# Jest

> Jest is a delightful JavaScript Testing Framework with a focus on simplicity.

Installing Jest is easy, run the command below and you will install Jest including its type definition and Typescript Jest runner:
```
npm i -D jest @types/jest ts-jest
```

Then we need to do some configuration, by initiating Jest to generate its config file, run:
```
jest --init
```

Answer the questions as needed, here is our answers:

**Choose the test environment that will be used for testing**
Node

**Do you want Jest to add coverage reports?**
No

**Which provider should be used to instrument code for coverage?**
Babel

**Automatically clear mock calls and instances between every test?**
Yes

This will generate a file called: `jest.config.js`:
```js
module.exports = {
    clearMocks: true,
    moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx'],
    roots: [
        "<rootDir>/src"
    ],
    testEnvironment: "node",
    transform: {
        '^.+\\.tsx?$': 'ts-jest'
    },
};
```

We added some additional configuration in order to get `jest` and `ts-jest` running in our environment.

`transform` is setup to look for `.ts` and `.tsx` files and use `ts-jest` to run it.
`moduleFileExtensions` has also been updated with `ts` and `tsx`.

### Alternative: Using preset to run ts-jest

Instead of configuring `transform` and `moduleFileExtensions` you can define a preset in your jest.config.js file.

`ts-jest` comes with 3 presets for you to use:
* `ts-jest/presets/default` or `ts-jest`: Its the default preset. TypeScript files will be handled by `ts-jest`, leaving JavaScript files as-is.

* `ts-jest/presets/js-with-ts`: Both TypeScript and JavaScript files will be handled by `ts-jest`.

* `ts-jest/presets/js-with-babel`: TypeScript files will be handled by `ts-jest` and JS files will be handled by Babel

`jest.config.js` will look like this when using default preset:
```js
module.exports = {
    clearMocks: true,
    roots: ['<rootDir>/src'],
    testEnvironment: 'node',
    preset: 'ts-jest'
};
```

Then go ahead and add a test script in your `package.json` file:
```json
"scripts": {
    //...
    "test": "jest"
},
```

Since we don't have any logic in our app, we are going to create a utility function just for this purpose in order to to write a test for it, let's create something that checks if a parameter is and number. Create a file `utils/isNumber.ts`:
```ts
export const isNumber = (n: any) => {
    return !isNaN(parseFloat(n)) && isFinite(n);
}
```

Now lets write a test for it, We prefer to add tests on the same place as our code, `utils/isNumber.test.ts`:

```ts
import {isNumber} from "./isNumber";

describe('isNumber Utils', () => {
    it('Its a number', () => {
        [0, 1, 2, -1, 1.345e17, '1'].map((n) => {
            expect(isNumber(n)).toEqual(true);
        });
    });

    it('Its not a number', () => {
        [false, true, NaN, [], {}, '1a'].map((n) => {
            expect(isNumber(n)).toEqual(false);
        });
    });
});
```

`describe` is used to group tests and `it` defines a test. You can run multiple tests under `it` but try to keep each test as small as possible for better readability. On the other hand `expect` is a Jest function that is used to check a value and its used "always" with a matcher function, in our case `toEqual`

The test above will test different inputs towards our function and fail/pass based on its return.

When testing the golden rule is:
> Cover all thinkable scenarios (not always possible) and create a test for it!

Jest is quite powerful so take a look into its [documentation](https://jestjs.io/docs/en/getting-started) and explore it further more.

If you have any questions please do ask!

# Supertest

With a fluent API, Supertest will help us test Node.js HTTP servers. Supertest is build upon Super-Agent Node.js library.

To install Supertest run:
```
npm i -D supertest @types/supertest
```

In order to test our app, we are going to do some refactoring, create an file under `src/app.ts`:
```ts
import express, { Application } from 'express';
import {routes} from "./routes";

// Boot express
export const app: Application = express();

// Application routing
routes(app);
```

Also we need/prefer to moved our routes definition into `src/routes.ts`:
```ts
import { Application, Router } from 'express';
import { PingController } from "./controllers/PingController";
import { IndexController } from "./controllers/IndexController";

const _routes: [string, Router][] = [
    ['/', IndexController],
    ['/ping', PingController],
];

export const routes = (app: Application) => {
    _routes.forEach((route) => {
        const [url, controller] = route;
        app.use(url, controller);
    });
};
```

This way we can start organize our application easier. Go ahead and create following controllers, first, `src/controllers/IndexController.ts`:
```ts
import { NextFunction, Request, Response, Router } from 'express';
export const IndexController: Router = Router();

IndexController.get('/', async (req: Request, res: Response, next: NextFunction) => {
    try {
        res.status(200).send({ data: 'Hello from Ornio AS!' });
    } catch (e) {
        next(e);
    }
});
```

And then `src/controllers/PingController.ts`:
```ts
import { NextFunction, Request, Response, Router } from 'express';
export const PingController: Router = Router();

PingController.get('/', async (req: Request, res: Response, next: NextFunction) => {
    try {
        res.status(200).send({ data: 'Pong!' });
    } catch (e) {
        next(e);
    }
});
```

And finally our `src/index.ts` file is refactored to this:
```ts
import {app} from "./app";

const port = 5000;

// Start server
app.listen(port, () => console.log(`Server is listening on port ${port}!`));
```

Now we can go ahead and create a test for testing our `PingController` using Supertest. `src/controller/PingController.test.ts`:
```ts
import request from 'supertest';
import { app } from '../app';

describe('Test PingController', () => {
  it('Request /ping should return Pong!', async () => {
    const result = await request(app).get('/ping').send();

    expect(result.status).toBe(200);
    expect(result.body.data).toBe('Pong!');
  });
});
```

We start with a normal Jest test by describing it and then we call `request(app)` chained using ´get('/ping')´ which is the route and then finally we use `send()` to send the request.

When the request is sent and result is populated with the data we then checked if the status is 200 and the `body.data` is equal to `Pong!`.

Supertest is quite powerful and can be used to send advance requests by modifying headers, generating/storing tokens etc. It supports all CRUD operations.

I strongly recommend you to take a look on their [documentation](https://github.com/visionmedia/supertest#readme) for more information and what Supertest can do.

Thats all for now. Until next time happy coding :)

# Source code
You can find the source code [here](https://github.com/ornio-no/post-nodejs-express-typescript-howto/tree/part-3).

# Need help?
Comment here or ping me on Twitter and I will gladly try to help you :)

___

# Whats next?
- Docerizing an Node.js/Typescript (this) application (Part 4)

___