Master testing in NestJS for building reliable applications with comprehensive unit, integration, and end-to-end tests.
Unit Testing Setup
Creating and configuring test modules with TestingModule.
import { Test, TestingModule } from '@nestjs/testing';
import { UserService } from './user.service';
import { getRepositoryToken } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
describe('UserService', () => {
let service: UserService;
let module: TestingModule;
beforeEach(async () => {
module = await Test.createTestingModule({
providers: [
UserService,
{
provide: getRepositoryToken(User),
useValue: {
find: jest.fn(),
findOne: jest.fn(),
save: jest.fn(),
create: jest.fn(),
delete: jest.fn(),
},
},
],
}).compile();
service = module.get<UserService>(UserService);
});
afterEach(async () => {
await module.close();
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('should find all users', async () => {
const users = [{ id: 1, name: 'John' }];
jest.spyOn(service, 'findAll').mockResolvedValue(users);
const result = await service.findAll();
expect(result).toEqual(users);
expect(service.findAll).toHaveBeenCalled();
});
});
// Custom provider testing
describe('ConfigService', () => {
let service: ConfigService;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
{
provide: ConfigService,
useFactory: () => {
return new ConfigService('.env.test');
},
},
],
}).compile();
service = module.get<ConfigService>(ConfigService);
});
it('should load config from test environment', () => {
expect(service.get('NODE_ENV')).toBe('test');
});
});
Testing Controllers
Mocking services and testing request/response handling.
import { Test, TestingModule } from '@nestjs/testing';
import { UserController } from './user.controller';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { NotFoundException } from '@nestjs/common';
describe('UserController', () => {
let controller: UserController;
let service: UserService;
const mockUserService = {
findAll: jest.fn(),
findOne: jest.fn(),
create: jest.fn(),
update: jest.fn(),
remove: jest.fn(),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [UserController],
providers: [
{
provide: UserService,
useValue: mockUserService,
},
],
}).compile();
controller = module.get<UserController>(UserController);
service = module.get<UserService>(UserService);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('findAll', () => {
it('should return an array of users', async () => {
const users = [
{ id: 1, name: 'John', email: 'john@example.com' },
{ id: 2, name: 'Jane', email: 'jane@example.com' },
];
mockUserService.findAll.mockResolvedValue(users);
const result = await controller.findAll();
expect(result).toEqual(users);
expect(service.findAll).toHaveBeenCalledTimes(1);
});
it('should return empty array when no users', async () => {
mockUserService.findAll.mockResolvedValue([]);
const result = await controller.findAll();
expect(result).toEqual([]);
});
});
describe('findOne', () => {
it('should return a user by id', async () => {
const user = { id: 1, name: 'John', email: 'john@example.com' };
mockUserService.findOne.mockResolvedValue(user);
const result = await controller.findOne('1');
expect(result).toEqual(user);
expect(service.findOne).toHaveBeenCalledWith(1);
});
it('should throw NotFoundException when user not found', async () => {
mockUserService.findOne.mockRejectedValue(
new NotFoundException('User not found'),
);
await expect(controller.findOne('999')).rejects.toThrow(
NotFoundException,
);
});
});
describe('create', () => {
it('should create a new user', async () => {
const createUserDto: CreateUserDto = {
name: 'John',
email: 'john@example.com',
password: 'password123',
};
const createdUser = { id: 1, ...createUserDto };
mockUserService.create.mockResolvedValue(createdUser);
const result = await controller.create(createUserDto);
expect(result).toEqual(createdUser);
expect(service.create).toHaveBeenCalledWith(createUserDto);
});
});
describe('update', () => {
it('should update a user', async () => {
const updateDto = { name: 'Updated Name' };
const updatedUser = { id: 1, name: 'Updated Name', email: 'john@example.com' };
mockUserService.update.mockResolvedValue(updatedUser);
const result = await controller.update('1', updateDto);
expect(result).toEqual(updatedUser);
expect(service.update).toHaveBeenCalledWith(1, updateDto);
});
});
describe('remove', () => {
it('should delete a user', async () => {
mockUserService.remove.mockResolvedValue({ deleted: true });
const result = await controller.remove('1');
expect(result).toEqual({ deleted: true });
expect(service.remove).toHaveBeenCalledWith(1);
});
});
});
Testing Services
Mocking repositories and database operations.
import { Test, TestingModule } from '@nestjs/testing';
import { UserService } from './user.service';
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';
import { getRepositoryToken } from '@nestjs/typeorm';
import { NotFoundException, ConflictException } from '@nestjs/common';
describe('UserService', () => {
let service: UserService;
let repository: Repository<User>;
const mockRepository = {
find: jest.fn(),
findOne: jest.fn(),
findOneBy: jest.fn(),
save: jest.fn(),
create: jest.fn(),
delete: jest.fn(),
update: jest.fn(),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UserService,
{
provide: getRepositoryToken(User),
useValue: mockRepository,
},
],
}).compile();
service = module.get<UserService>(UserService);
repository = module.get<Repository<User>>(getRepositoryToken(User));
});
describe('findAll', () => {
it('should return an array of users', async () => {
const users = [{ id: 1, name: 'John', email: 'john@example.com' }];
mockRepository.find.mockResolvedValue(users);
const result = await service.findAll();
expect(result).toEqual(users);
expect(repository.find).toHaveBeenCalled();
});
});
describe('findOne', () => {
it('should return a user', async () => {
const user = { id: 1, name: 'John', email: 'john@example.com' };
mockRepository.findOneBy.mockResolvedValue(user);
const result = await service.findOne(1);
expect(result).toEqual(user);
expect(repository.findOneBy).toHaveBeenCalledWith({ id: 1 });
});
it('should throw NotFoundException when user not found', async () => {
mockRepository.findOneBy.mockResolvedValue(null);
await expect(service.findOne(999)).rejects.toThrow(NotFoundException);
});
});
describe('create', () => {
it('should create a new user', async () => {
const createDto = {
name: 'John',
email: 'john@example.com',
password: 'password123',
};
const user = { id: 1, ...createDto };
mockRepository.findOneBy.mockResolvedValue(null); // Email not taken
mockRepository.create.mockReturnValue(user);
mockRepository.save.mockResolvedValue(user);
const result = await service.create(createDto);
expect(result).toEqual(user);
expect(repository.create).toHaveBeenCalledWith(createDto);
expect(repository.save).toHaveBeenCalledWith(user);
});
it('should throw ConflictException when email exists', async () => {
const createDto = {
name: 'John',
email: 'john@example.com',
password: 'password123',
};
mockRepository.findOneBy.mockResolvedValue({ id: 1 }); // Email exists
await expect(service.create(createDto)).rejects.toThrow(
ConflictException,
);
});
});
describe('update', () => {
it('should update a user', async () => {
const updateDto = { name: 'Updated Name' };
const existingUser = { id: 1, name: 'John', email: 'john@example.com' };
const updatedUser = { ...existingUser, ...updateDto };
mockRepository.findOneBy.mockResolvedValue(existingUser);
mockRepository.save.mockResolvedValue(updatedUser);
const result = await service.update(1, updateDto);
expect(result).toEqual(updatedUser);
expect(repository.save).toHaveBeenCalled();
});
});
describe('remove', () => {
it('should delete a user', async () => {
const user = { id: 1, name: 'John', email: 'john@example.com' };
mockRepository.findOneBy.mockResolvedValue(user);
mockRepository.delete.mockResolvedValue({ affected: 1 });
await service.remove(1);
expect(repository.delete).toHaveBeenCalledWith(1);
});
it('should throw NotFoundException when deleting non-existent user', async () => {
mockRepository.findOneBy.mockResolvedValue(null);
await expect(service.remove(999)).rejects.toThrow(NotFoundException);
});
});
});
Testing Providers
Factory providers and async providers.
import { Test, TestingModule } from '@nestjs/testing';
import { ConfigService } from '@nestjs/config';
import { DatabaseService } from './database.service';
describe('Factory Providers', () => {
let databaseService: DatabaseService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: 'DATABASE_CONNECTION',
useFactory: (config: ConfigService) => {
return {
host: config.get('DB_HOST'),
port: config.get('DB_PORT'),
database: config.get('DB_NAME'),
};
},
inject: [ConfigService],
},
{
provide: ConfigService,
useValue: {
get: jest.fn((key: string) => {
const config = {
DB_HOST: 'localhost',
DB_PORT: 5432,
DB_NAME: 'test_db',
};
return config[key];
}),
},
},
DatabaseService,
],
}).compile();
databaseService = module.get<DatabaseService>(DatabaseService);
});
it('should create database connection with correct config', () => {
const connection = databaseService.getConnection();
expect(connection.host).toBe('localhost');
expect(connection.port).toBe(5432);
expect(connection.database).toBe('test_db');
});
});
// Async provider testing
describe('Async Providers', () => {
let service: any;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: 'ASYNC_CONNECTION',
useFactory: async () => {
await new Promise((resolve) => setTimeout(resolve, 100));
return { connected: true };
},
},
],
}).compile();
service = module.get('ASYNC_CONNECTION');
});
it('should resolve async provider', () => {
expect(service.connected).toBe(true);
});
});
Testing Guards
Authentication and authorization guards.
import { Test, TestingModule } from '@nestjs/testing'; import { JwtAuthGuard } from './jwt-auth.guard'; import { JwtService } from '@nestjs/jwt'; import { ExecutionContext, UnauthorizedException } from '@nestjs/common';
describe('JwtAuthGuard', () => { let guard: JwtAuthGuard; let jwtService: JwtService;
const mockJwtService <span class="token