Unit Tests for Node.js APIs built with TS, Express.js and TypeORM
Tags: node, beginners, tutorial, webdev
Published: 2020-01-08 19:27:06

---
title: Unit Tests for Node.js APIs built with TS, Express.js and TypeORM
published: true
description: 
tags: node, beginners, tutorial, webdev
---

This post was originally published on my [blog](https://larswaechter.dev/blog/nodejs-rest-api-testing/).

---

Some days ago I wrote an [article] (https://dev.to/larswaechter/how-i-structure-my-rest-apis-11k4) about how I structure my Node.js REST APIs. However, I didn’t cover any test scenarios in there. So it’s time to catch up on this now.

We’re going to write an unit test for a single API component based on the project structure from my other article. The goal is to test the component by mocking a database and sending an HTTP request to its routes.

For writing tests I use the following node modules:
- Mocha
- Chai
- Supertest


### Project structure
This is the project structure I mentioned above. Of course, you can use any other as well.

```
nodejs-api-structure
└───src
   │
   └───config
   │
   └───api
   │   │
   │   └───components
   │   │   │
   │   │   └───user
   │   │       │   controller.ts
   │   │       │   model.ts
   │   │       │   routes.ts
   │   │       │   service.ts
   │   │       │   user.spec.ts
   │   │
   │   └───middleware
   │   │
   │   │   routes.ts
   │   │   server.ts
   │
   └───test
   │   │   factory.ts
   │
   │   index.ts
```

We'll focus on the following files:
- factory.ts
- user.spec.ts


### Test Factory (factory.ts)
This file is some kind of a setup file for each single unit test. It takes care of the database connection and starts the Express.js server.

We use ‘sqljs’ as database type, so it’s not necessary to provide a real database like MySQL or any other.

The code should be self explanatory. The class acts like a container for the database connection and express server. It provides getter methods to make them accessible and a method to open / close the connections.

```typescript
import 'reflect-metadata';
import 'source-map-support/register';
import 'module-alias/register';

// Set env to test
process.env.NODE_ENV = 'test';

// Set env variables from .env file
import { config } from 'dotenv';
config();

import { createConnection, ConnectionOptions, Connection } from 'typeorm';
import { createServer, Server as HttpServer } from 'http';

import express from 'express';
import supertest from 'supertest';

import { env } from '@config/globals';

import { Server } from '../api/server';

/**
 * TestFactory
 * - Loaded in each unit test
 * - Starts server and DB connection
 */

export class TestFactory {
	private _app: express.Application;
	private _connection: Connection;
	private _server: HttpServer;

	// DB connection options
	private options: ConnectionOptions = {
		type: 'sqljs',
		database: new Uint8Array(),
		location: 'database',
		logging: false,
		synchronize: true,
		entities: ['dist/api/components/**/model.js']
	};

	public get app(): supertest.SuperTest<supertest.Test> {
		return supertest(this._app);
	}

	public get connection(): Connection {
		return this._connection;
	}

	public get server(): HttpServer {
		return this._server;
	}

	/**
	 * Connect to DB and start server
	 */
	public async init(): Promise<void> {
	    this._connection = await createConnection(this.options);
		this._app = new Server().app;
		this._server = createServer(this._app).listen(env.NODE_PORT);
	}

	/**
	 * Close server and DB connection
	 */
	public async close(): Promise<void> {
		this._server.close();
		this._connection.close();
	}
}

```


### Component test (user.spec.ts)

This file covers the unit test for the API component. In there, we use different HTTP request methods like _POST_, _PUT_, _GET_ and _DELETE_ to test the component’s API endpoints.

First of all, we create a new instance of the `TestFactory` class and `User` model. The `mockTestUser` methods returns an instance of `User` including some dummy data. Moreover we create another instance `testUserModified` with some modified properties which will be used to test the _PUT_ endpoints.

```typescript
const factory: TestFactory = new TestFactory();
const testUser: User = User.mockTestUser();
const testUserModified: User = { ...testUser, firstname: 'testFirstnameModified', lastname: 'testLastnameModified' };
```

Now we define Mocha’s `before` and `after` methods. `before` is executed before the test starts and `after` is executed after the test has ended.

Inside them we call the factory’s `init` and `close` method which establish a new database connection and express server before the test starts and disconnects from it when he has ended.

```typescript
before(async () => {
	await factory.init();
});

after(async () => {
	await factory.close();
});
```

One important thing to notice is when you have multiple unit tests, that each test establishes a new database connection and express server.

For making HTTP requests to the server I use Supertest and Chai for validating the server responses.

Here's the complete code for one component:

```typescript
import 'module-alias/register';

import { assert } from 'chai';

import { User } from './model';
import { TestFactory } from '../../../../test/factory';

describe('Testing user component', () => {
    // Create instances
	const factory: TestFactory = new TestFactory();
	const testUser: User = User.mockTestUser();
	const testUserModified: User = { ...testUser, firstname: 'testFirstnameModified', lastname: 'testLastnameModified' };

	before(async () => {
		await factory.init();
	});

	after(async () => {
		await factory.close();
	});

	describe('POST /users', () => {
		it('responds with status 400', (done) => {
			factory.app
				.post('/api/v1/users')
				.send()
				.set('Accept', 'application/json')
				.expect('Content-Type', /json/)
				.expect(400, done);
		});

		it('responds with new user', (done) => {
			factory.app
				.post('/api/v1/users')
				.send({
					user: testUser
				})
				.set('Accept', 'application/json')
				.expect('Content-Type', /json/)
				.expect(200)
				.end((err, res) => {
					try {
						if (err) throw err;

						const { status } = res.body;
						const user: User = res.body.data;

						// Assert status
						assert(status === res.status, 'status does not match');

						// Assert user
						assert.isObject(user, 'user should be an object');
						assert(user.id === testUser.id, 'userID does not match');
						assert(user.email === testUser.email, 'userEmail does not match');
						assert(user.firstname === testUser.firstname, 'userFirstname does not match');
						assert(user.lastname === testUser.lastname, 'userLastname does not match');

						return done();
					} catch (err) {
						return done(err);
					}
				});
		});
	});

	describe('PUT /users/1', () => {
		it('responds with updated user', (done) => {
			factory.app
				.put('/api/v1/users/1')
				.send({
					user: testUserModified
				})
				.set('Accept', 'application/json')
				.expect('Content-Type', /json/)
				.end((err, res) => {
					try {
						if (err) throw err;

						const { status } = res.body;
						const user: User = res.body.data;

						// Assert status
						assert(status === res.status, 'status does not match');

						// Assert user
						assert.isObject(user, 'user should be an object');
						assert(user.id === testUserModified.id, 'userID does not match');
						assert(user.email === testUserModified.email, 'userEmail does not match');
						assert(user.firstname === testUserModified.firstname, 'userFirstname does not match');
						assert(user.lastname === testUserModified.lastname, 'userLastname does not match');

						return done();
					} catch (err) {
						return done(err);
					}
				});
		});
	});

	describe('GET /users', () => {
		it('responds with user array', (done) => {
			factory.app
				.get('/api/v1/users')
				.set('Accept', 'application/json')
				.expect('Content-Type', /json/)
				.expect(200)
				.end((err, res) => {
					try {
						if (err) throw err;

						const { status } = res.body;
						const users: User[] = res.body.data;

						// Assert status
						assert(status === res.status, 'status does not match');

						// Assert users
						assert.isArray(users, 'users should be an array');
						assert(users[0].id === testUserModified.id, 'userID does not match');
						assert(users[0].email === testUserModified.email, 'userEmail does not match');
						assert(users[0].firstname === testUserModified.firstname, 'userFirstname does not match');
						assert(users[0].lastname === testUserModified.lastname, 'userLastname does not match');

						return done();
					} catch (err) {
						return done(err);
					}
				});
		});
	});

	describe('GET /users/1', () => {
		it('responds with single user', (done) => {
			factory.app
				.get('/api/v1/users/1')
				.set('Accept', 'application/json')
				.expect('Content-Type', /json/)
				.expect(200)
				.end((err, res) => {
					try {
						if (err) throw err;

						const { status } = res.body;
						const user: User = res.body.data;

						// Assert status
						assert(status === res.status, 'status does not match');

						// Assert user
						assert.isObject(user, 'user should be an object');
						assert(user.id === testUserModified.id, 'userID does not match');
						assert(user.email === testUserModified.email, 'userEmail does not match');
						assert(user.firstname === testUserModified.firstname, 'userFirstname does not match');
						assert(user.lastname === testUserModified.lastname, 'userLastname does not match');

						return done();
					} catch (err) {
						return done(err);
					}
				});
		});
	});

	describe('DELETE /users/1', () => {
		it('responds with status 204', (done) => {
			factory.app
				.delete('/api/v1/users/1')
				.set('Accept', 'application/json')
				.expect(204, done);
		});

		it('responds with status 404', (done) => {
			factory.app
				.delete('/api/v1/users/1')
				.set('Accept', 'application/json')
				.expect(404, done);
		});
	});
});

```

---- 

That's it! I hope this is a little help for people who struggle with writing unit tests for REST APIs built with Express.js and TypeORM.

I'm currently working on a side project where some components cover unit test as I described here. [Have a look!] (https://github.com/Aionic-Apps/aionic-core/tree/master) at the 'user' or 'task' component for example.