Part 4.1 — Setting Up Jest, Test Environment & Prisma Test DB
Testing a backend requires more than installing Jest and writing a few assertions.
Your tests need:
a dedicated test database, isolated from development
a repeatable schema reset workflow
testing modules that bootstrap NestJS correctly
integration with Supertest
a structure that scales as your backend grows
In this post, you'll set up all the essential machinery that makes backend testing predictable and safe.
1. Install Testing Dependencies
NestJS comes with basic Jest config, but you need a bit more for backend testing:
pnpm add -D @nestjs/testing jest ts-jest supertest @types/supertestInitialize Jest (if not already created):
pnpm ts-jest config:initYou now have a jest.config.js.
2. Create a Dedicated Test Database (PostgreSQL)
You must never run tests against your dev or production DB.
Add a separate Postgres service for tests:
# docker-compose.yml
services:
db:
image: postgres:15
ports:
- "5432:5432"
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: fullstack_db
db_test:
image: postgres:15
ports:
- "5433:5432"
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: fullstack_test_dbStart it:
docker compose up -d db_testYour test DB is now available on port 5433.
3. Create a .env.test Environment File
At the root of your backend:
DATABASE_URL="postgresql://postgres:password@localhost:5433/fullstack_test_db"
NODE_ENV=test
PORT=4000This ensures tests run fully isolated.
4. Configure Jest to Use .env.test
Update jest.config.js:
process.env.NODE_ENV = 'test';
require('dotenv').config({ path: '.env.test' });
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
moduleFileExtensions: ['js', 'json', 'ts'],
rootDir: '.',
testRegex: '.*\\.spec\\.ts$',
coverageDirectory: './coverage',
};Now Jest always loads the correct DB URL.
5. Generate a Testing Module for NestJS
Whenever you test anything involving NestJS, create a module:
import { Test } from '@nestjs/testing';
import { AppModule } from '../src/app.module';
describe('AppModule', () => {
it('initializes correctly', async () => {
const module = await Test.createTestingModule({
imports: [AppModule],
}).compile();
const app = module.createNestApplication();
await app.init();
expect(app).toBeDefined();
});
});This ensures:
pipes
filters
interceptors
providers
are all wired correctly.
6. Ensure Prisma Works in Testing Context
You need a clean test database before each test suite.
Create a utility: test/utils/prisma-test.ts
import { PrismaClient } from '@prisma/client';
export const prisma = new PrismaClient();
export async function resetDb() {
await prisma.$transaction([
prisma.post.deleteMany(),
prisma.user.deleteMany(),
]);
}This is fine for small projects.
For larger suites, you’ll use transactions (covered in Part 4.5).
7. Run Prisma Migrations on the Test DB
Before tests boot:
pnpm prisma migrate deploy --schema=prisma/schema.prismaYou can automate this in Jest’s global setup:
Create: jest.global-setup.ts
import { execSync } from 'child_process';
module.exports = async () => {
execSync('pnpm prisma migrate deploy', { stdio: 'inherit' });
};Add to config:
globalSetup: './jest.global-setup.ts',Now every test suite runs against a pristine schema.
8. Using Supertest for Integration Requests
Install Supertest (already done), then test your controllers:
import request from 'supertest';
import { Test } from '@nestjs/testing';
import { AppModule } from '../src/app.module';
describe('Users (e2e)', () => {
let app;
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = module.createNestApplication();
await app.init();
});
it('creates a user', async () => {
const res = await request(app.getHttpServer())
.post('/users')
.send({ name: 'Bob', email: 'bob@example.com' })
.expect(201);
expect(res.body.email).toBe('bob@example.com');
});
});This test touches:
NestJS controller
DTO validation
pipes
Prisma
test database
global interceptors
error filters
True integration testing.
9. Ensure Test Cleanup
After running tests, shut down Prisma:
afterAll(async () => {
const { prisma } = await import('./test/utils/prisma-test');
await prisma.$disconnect();
});Without this, Jest may hang.
10. Add test Script to package.json
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:ci": "jest --runInBand"
}Note:--runInBand ensures predictable behavior in CI environments.
11. Summary
You now have:
A dedicated PostgreSQL test database
.env.testenvironmentJest test configuration
Global setup for Prisma migrations
Testing modules for Nest
Supertest for full-stack endpoint testing
Repeatable DB reset utilities
Clean test lifecycle management
Related
Part 3.4 — Error Handling, Pipes, Filters & Global Interceptors
Production backends thrive on predictable errors, structured validation, and consistent responses. In this post, you’ll learn how to build those foundations using NestJS pipes, filters, and interceptors.
Part 3.3 — Prisma Queries, Relations & Best Practices
This post teaches you how to design efficient, relational Prisma queries while avoiding N+1 errors, modeling relations cleanly, and thinking like a PostgreSQL-first backend engineer.
Part 3.2 — Building REST Controllers, Services & DTO Validation
Clean REST architecture starts with controllers, services, and DTO validation. In this post, you’ll build the structure behind every API endpoint you’ll create moving forward.
Comments