Unit tests Nest.js
I've been meaning to write about this topic for quite some time now, in fact I think I started this whole blog thing just to write about how to do unit test right on Nest.js + postgres. I hope it can live up to the expectation I'm setting.
To do this right, we are going to be talking about what are unit tests conceptually, why they are important, and how can we provide a guarantee that our code works by using them.
Introducing a real life example
The best way I could come up with to explain this concept is by working on a real life example. We are going to work on a Marketplace system, where users can spend their points to buy products. Specifically we will be treating one isolated scenario: placing an order to purchase a product. Here it's a diagram to put altogether

Despite being from a real project, I've decided to omit most of the details for the sake of simplicity. Let's assume our system already has users created who can sign up, log in, earn points, and everything else that is needed for this system to make sense.
Here are our 3 entities on TypeORM:
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column({ unique: true })
username: string
@Column({ type: 'integer', default: 0 })
balance: number
}
user.entity.ts
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
@Entity()
export class MarketplaceItem {
@PrimaryGeneratedColumn()
id: number
@Column()
title: string
@Column({ nullable: true })
description?: string
@Column()
price: number
@Column({ nullable: true })
imageUrl: string
@Column({ default: 0 })
stock: number
@Column({ default: false })
hidden: boolean
}
marketplace-item.entity.ts
import { Column, CreateDateColumn, Entity, ManyToOne, PrimaryGeneratedColumn, Relation } from "typeorm";
import { User } from "./user.entity";
import { MarketplaceItem } from "./marketplace-item.entity";
export enum MarketplaceOrderStatus {
PENDING = 'PENDING',
COMPLETED = 'COMPLETED',
}
@Entity()
export class MarketplaceOrder {
@PrimaryGeneratedColumn()
id: number
@Column({ type: "varchar", default: MarketplaceOrderStatus.PENDING })
status: MarketplaceOrderStatus
@ManyToOne(() => User)
buyer: Relation<User>
@ManyToOne(() => MarketplaceItem)
item: Relation<MarketplaceItem>
@CreateDateColumn({ type: 'timestamptz' })
createdAt: Date
}
marketplace-order.entity.ts
Don't worry I'll provide a full project repo at the end of the blog 😉
Let's jump into the interesting stuff; writing our function to place an order with some business validations:

Let's go through it very quickly:
- We have a nest.js service with the necessary repositories to access these entities
- On our service we check if the user exists
- Then we check if the item exists (and if it's visible)
- We make sure that the item is available
- We ensure that user has enough funds to buy it
- We open a transaction, since we are modifying two different tables and:
- Decrement user balance
- store the order on the database
Now... how do we know that this works? Actually we don't, to test it we would have to seed the database, create a controller, expose this service and hit it with a tool like Postman. But that's too much trouble and for every change we made we would have to do that all over again.
Luckily there's a way to do this, and yes I've already spoiled it at the beginning of the post: Unit Tests.
What is unit testing?
So I'm not going to give you the "book" definition because you probably already know that, instead I'm going to tell you what it gives you and what it should do for you.
Unit tests should test your business logic and give you a guarantee that the code you wrote does what you expect it to do. Every time you make a change on the code they will tell you if you broke something.
A few considerations:
- Unit tests should test a small isolated piece of code
- Should test that the code does what it's intended to do
- Should run fast so that it removes the developer temptation to avoid running them
- Should have a description of the case that it's trying to test
- Every unit test should be independent from the rest
What to test?
This is probably one of the most important questions you could end up with, and depending on who you ask you might end up with very different answers. My take here is that with unit tests you should test business logic and helper functions, you should not test libraries. I also do not test controllers, I leave those for E2E tests.
But what about database queries? The placeOrder()
function we wrote earlier is a clear example of this, it has database logic embedded with business logic. So what should we do about that?
Engineering books suggest that you should abstract database layer from service layer so that if it ever change it doesn't break your service's tests. Nowadays this is somewhat far from reality, chances are that 90% of the projects you work on will have the same database for it's lifetime (and 99% of those database will probably be postgres).
Also abstracting some business logic from the database query sometimes ends up removing some of the performance advantages that database provides us (for example trying to remove a where condition and having to fetch the whole table to filter in memory). And if we still wanted to put that logic on the database and follow the books suggestions, we would end up needing to mock those calls hence defeating the purpose of the unit test: to test business logic.
How to test database logic?
Some of you might probably be thinking that in order to test database queries we would need to spin up a test database server and seed it, heck I've even seen many udemy courses that suggest this. What if I tell you there is a better way to do so? And have peace of mind that your database queries will also work. It exists, and for postgresql it's called pg-mem
, it's an in-memory database that simulates postgres and analyze SQL queries, and the best of it? It works with TypeORM and many other ORMs.
Writing our first unit tests
Let's first start by writing the basic structure to test a service on nest.js with jest.
marketplace.service.ts
our test file will be called marketplace.spec.ts
import { Test, TestingModule } from '@nestjs/testing'
import { MarketplaceService } from './marketplace.service'
describe('MarketplaceService', () => {
let moduleRef: TestingModule
let service: MarketplaceService
beforeAll(async () => {
moduleRef = await Test.createTestingModule({
imports: [],
providers: [
MarketplaceService
]
}).compile()
service = moduleRef.get(MarketplaceService)
})
describe('placeOrder', () => { })
})
marketplace.spec.ts
To test the way nest.js does things, we need to create a testing module, that basically acts the same as any other module you use on nestjs to pack things, which means that we are in charge of telling which are the providers for this module to work.
You might have noticed that we are missing all the database providers that our service uses which are:
- Repository<User>
- Repository<MarketplaceItem>
- Repository<MarketplaceOrder>
- DataSource
So let's write a helper function that allows us to simulate a close-to-real database. I promise it will be small.
import { MarketplaceItem } from '@/models/marketplace-item.entity'
import { MarketplaceOrder } from '@/models/marketplace-order.entity'
import { User } from '@/models/user.entity'
import { Provider } from '@nestjs/common'
import { TypeOrmModuleOptions, getRepositoryToken } from '@nestjs/typeorm'
import { EntityClassOrSchema } from '@nestjs/typeorm/dist/interfaces/entity-class-or-schema.type'
import { DataType, IMemoryDb, newDb } from 'pg-mem'
import { DataSource } from 'typeorm'
import { SnakeNamingStrategy } from 'typeorm-naming-strategies'
import { v4 } from 'uuid'
const entities: EntityClassOrSchema[] = [
MarketplaceItem,
MarketplaceOrder,
User
]
export class TestingDatabase {
private db: IMemoryDb
private dataSource: DataSource
private readonly typeormConfig: TypeOrmModuleOptions = {
type: 'postgres',
entities,
namingStrategy: new SnakeNamingStrategy(),
useUTC: true
}
public constructor() {
this.db = newDb({ autoCreateForeignKeyIndices: true })
this.db.public.registerFunction({
implementation: () => 'test',
name: 'current_database'
})
this.db.public.registerFunction({
implementation: () => 'test',
name: 'version'
})
this.db.registerExtension('uuid-ossp', (schema) => {
schema.registerFunction({
name: 'uuid_generate_v4',
returns: DataType.uuid,
implementation: v4,
impure: true
})
})
this.db.public.registerFunction({
name: 'exists',
implementation: (...args: unknown[]) => {
return args.length > 0
},
returns: DataType.bool,
argsVariadic: DataType.integer
})
}
public async initialize(): Promise<{
db: IMemoryDb
databaseProviders: Provider[]
datasource: DataSource
}> {
this.dataSource = await this.db.adapters.createTypeormDataSource(this.typeormConfig)
await this.dataSource.initialize()
await this.dataSource.synchronize()
const databaseProviders = [
{
provide: DataSource,
useValue: this.dataSource
},
...entities.map((entity) => ({
provide: getRepositoryToken(entity),
useValue: this.dataSource.getRepository(entity)
}))
]
return {
databaseProviders,
db: this.db,
datasource: this.dataSource
}
}
}
db-test.ts
I wrote it in a class but you can write it as a function if you prefer. Let's go through it:
- We define an array containing all our entities
- We call pg-mem's
newDb()
- Since there are some postgres functions that are not implemented by pg-mem, you can implement it youself by calling
registerFunction
- current_database()
- version()
- uuid_generate_v4()
- exists()
- Then we have the
initialize()
method that creates the TypeORM adapter, loads the entities withsynchronize()
, after that we create a repository and a nestjsProvider
for each Entity. Finally we return an object with everything we need.
Here it's how to use it on our marketplace.spec.ts
describe('MarketplaceService', () => {
let moduleRef: TestingModule
let service: MarketplaceService
beforeAll(async () => {
const testingDatabase = new TestingDatabase()
const { db, databaseProviders } = await testingDatabase.initialize()
moduleRef = await Test.createTestingModule({
imports: [],
providers: [
MarketplaceService,
...databaseProviders,
]
}).compile()
service = moduleRef.get(MarketplaceService)
})
describe('placeOrder', () => { })
})
marketplace.spec.ts
It'd be nice if for each test we could have an initial database state, like having the user already created, we can do that by creating a backup from the database and restoring it after each test:
import { Test, TestingModule } from '@nestjs/testing'
import { MarketplaceService } from './marketplace.service'
import { TestingDatabase } from '@/config/db-test'
import { IBackup } from 'pg-mem'
describe('MarketplaceService', () => {
let moduleRef: TestingModule
let service: MarketplaceService
let datasource: DataSource
let user: User
let backup: IBackup
beforeAll(async () => {
const testingDatabase = new TestingDatabase()
const { db, databaseProviders, datasource: ds } = await testingDatabase.initialize()
datasource = ds
moduleRef = await Test.createTestingModule({
imports: [],
providers: [
MarketplaceService,
...databaseProviders,
]
}).compile()
service = moduleRef.get(MarketplaceService)
user = await datasource.manager.save(
datasource.manager.create(User, { username: 'test', balance: 0 })
)
backup = db.backup()
})
afterEach(() => {
backup.restore()
})
describe('placeOrder', () => { })
})
marketplace.spec.ts
I've added a global reference to the datasource
so that we can use it later to perform operations to the database. In the mean time I've used that same reference to call the manager
and create a user right before calling db.backup
so that after each test the database clears itself and the only data left will be that user.
backup.restore()
function(quoted from pg-mem)
Restores data to the state when this backup has been performed. 👉 This operation is O(1). 👉 Schema must not have been changed since then !
We have all in place to start our tests, let's set some expectations on what to test:

A nice way to write tests is by testing the function step by step. If you recall the first step is check if the user exists, and then it checks if the item exist and so on, and the way I wrote my tests matches those steps in the exact same order.
it('Should throw an error if the buyer was not found', async () => {
const fakeId = 10
const fakeItemId = 5
await expect(service.placeOrder(fakeItemId, fakeId)).rejects.toThrow(
'User not found'
)
})
Test #1
Our first test requires us to test that the placeOrder
method can handle situations where the user does not exists, in this case, by throwing an error. We use the rejects
to wait for an error thrown by a promise.

One down, five left. (notice how the test itself only took 8ms)
it('Should throw an error if the item does not exists', async () => {
const fakeItemId = 5
await expect(service.placeOrder(fakeItemId, user.id)).rejects.toThrow(
'Item not found'
)
})
Test #2
Here we are using the user we created on the beforeAll()
function
it('Should throw an error if the item is hidden', async () => {
const item = await datasource.manager.save(
datasource.manager.create(MarketplaceItem, {
title: 'test item',
description: 'test',
price: 100,
imageUrl: 'http://testimage.com',
stock: 0,
hidden: true
}))
await expect(service.placeOrder(item.id, user.id)).rejects.toThrow(
'Item not found'
)
})
Test #3
Now we are using the datasource
to create an item on the test, this insertion on the database will be deleted as soon as the test ends, because the backup.restore()
will be triggered.
it('Should throw an error if the item\'s stock is 0', async () => {
const item = await datasource.manager.save(
datasource.manager.create(MarketplaceItem, {
title: 'test item',
description: 'test',
price: 100,
imageUrl: 'http://testimage.com',
stock: 0,
hidden: false
}))
await expect(service.placeOrder(item.id, user.id)).rejects.toThrow(
'Item has no stock'
)
})
Test #4
This test is the same as the previous one, we only changed hidden
to false
it('Should throw an error if the buyer does not have enough funds', async () => {
const item = await datasource.manager.save(
datasource.manager.create(MarketplaceItem, {
title: 'test item',
description: 'test',
price: 100,
imageUrl: 'http://testimage.com',
stock: 1,
hidden: false
}))
await expect(service.placeOrder(item.id, user.id)).rejects.toThrow(
'User does not have enough funds'
)
})
Test #5
We create an item with a stock of 1 so that it passes the test, and since we created a user with a balance of 0
we don't need to make anything else to pass this test
it('Should place the order', async () => {
const item = await datasource.manager.save(
datasource.manager.create(MarketplaceItem, {
title: 'test item',
description: 'test',
price: 100,
imageUrl: 'http://testimage.com',
stock: 1,
hidden: false
}))
await datasource.manager.update(User, user.id, { balance: 101 })
const order = await service.placeOrder(item.id, user.id)
expect(order.id).toBeDefined()
expect(order.buyer.id).toBe(user.id)
expect(order.item.id).toBe(item.id)
expect(order.status).toBe(MarketplaceOrderStatus.PENDING)
const updatedUser = await datasource.manager.findOne(User, { where: { id: user.id } })
expect(updatedUser.balance).toBe(1)
})
Test #6
So for our final test we updated our user balance to 101
and then we expect the following things from the service:
- To return an order
- That the buyer id and item id matches our input
- the status of the order is Pending
- For the user balance to be 1
Jest provides another way that we can also use to expect an specific object with matching conditions
it('Should place the order', async () => {
const item = await datasource.manager.save(
datasource.manager.create(MarketplaceItem, {
title: 'test item',
description: 'test',
price: 100,
imageUrl: 'http://testimage.com',
stock: 1,
hidden: false
}))
await datasource.manager.update(User, user.id, { balance: 101 })
const order = await service.placeOrder(item.id, user.id)
expect(order).toMatchObject({
id: expect.any(Number),
buyer: expect.objectContaining({ id: user.id }), // we don't care about the rest
item: expect.objectContaining({ id: item.id }),
status: MarketplaceOrderStatus.PENDING
})
const updatedUser = await datasource.manager.findOne(User, { where: { id: user.id } })
expect(updatedUser.balance).toBe(1)
})

The whole execution took only 50 milliseconds, quite impressive right? Well I'm gonna show you what it looks like for >800 tests on a real world application:

Test coverage
So jest also allows us to generate a testing report to identify which % of code is tested, we can do this by running npx jest --coverage

It also generates us an html page that allows us to analyze our code deeper and see which specific lines we are not testing. this can be found on coverage/index.html
on this repo after executing the coverage command.


Let's comment out text number #6 and see what's the output of this:

The console already tells us that some lines are not being tested, let's see what the report says

As you can see, it highlights the functions that are not being tested, meaning we cannot guarantee that piece of code does what it's intended to do, the only way to guarantee this is by writing a test.
Unit tests should test your business logic and give you a guarantee that the code you wrote does what you expect it to do.
Project repository
Final words
If you made it this far you have definitely noticed that this post isn't about how to set up a testing environment for Nest.js or how to configure Jest. Instead, this was about highlighting the importance of unit tests, and how easily one can test functionality without having to mock every database call.
Let's face it, database logic is embedded in our apps, and we should embrace testing it rather than setting it apart. After all, most projects don't usually switch database that easily, and if they do, they're likely large enough to handle the cost of such a change
I could write a ton more about this topic, but I think this post is already long enough, I might cover setups and better practice in a future post, for that you can subscribe to be notified once I publish it 😉 . Thanks for reading.