En esta página
Testing en NestJS — unitario y end-to-end
La filosofía del testing en NestJS
NestJS fue diseñado desde el principio para ser testeable. Su sistema de inyección de dependencias hace trivial reemplazar implementaciones reales con mocks en los tests. La combinación de @nestjs/testing con Jest crea una experiencia de testing de primera clase.
Existen tres niveles de testing en NestJS:
- Tests unitarios: Prueban un servicio o componente en aislamiento, con todas sus dependencias mockeadas
- Tests de integración: Prueban un módulo completo con algunos servicios reales y otros mockeados
- Tests end-to-end (e2e): Prueban la aplicación completa desde la perspectiva HTTP, incluyendo base de datos
Configuración de Jest
NestJS configura Jest automáticamente al crear el proyecto. El package.json incluye:
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"jest": {
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": { "^.+\\.(t|j)s$": "ts-jest" },
"collectCoverageFrom": ["**/*.(t|j)s"],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}El TestingModule — La pieza central
Test.createTestingModule() crea un módulo NestJS especial para testing. Es como @Module() pero diseñado para ser construido dinámicamente en los tests:
import { Test, TestingModule } from '@nestjs/testing';
describe('UsuariosService', () => {
let module: TestingModule;
let service: UsuariosService;
beforeEach(async () => {
module = await Test.createTestingModule({
providers: [
UsuariosService,
{ provide: getRepositoryToken(Usuario), useValue: mockRepo },
{ provide: EmailService, useValue: mockEmailService },
],
}).compile();
service = module.get<UsuariosService>(UsuariosService);
});
afterEach(async () => {
await module.close();
});
});Estrategias de mocking
Mock de repositorios de TypeORM
const mockRepo = {
find: jest.fn(),
findOne: jest.fn(),
findOneOrFail: jest.fn(),
save: jest.fn(),
create: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
softDelete: jest.fn(),
createQueryBuilder: jest.fn().mockReturnValue({
where: jest.fn().mockReturnThis(),
andWhere: jest.fn().mockReturnThis(),
orderBy: jest.fn().mockReturnThis(),
skip: jest.fn().mockReturnThis(),
take: jest.fn().mockReturnThis(),
getManyAndCount: jest.fn().mockResolvedValue([[], 0]),
}),
};Mock de servicios con auto-mocking
const module = await Test.createTestingModule({
providers: [ProductosService],
})
.useMocker((token) => {
// Auto-mockea automáticamente todas las dependencias
if (typeof token === 'function') {
const mockMetadata = moduleMocker.getMetadata(token);
const Mock = moduleMocker.generateFromMetadata(mockMetadata);
return new Mock();
}
return {};
})
.compile();Testing del controlador
Para tests de controladores, puedes testear sin levantar el servidor HTTP:
// productos/productos.controller.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { ProductosController } from './productos.controller';
import { ProductosService } from './productos.service';
describe('ProductosController', () => {
let controller: ProductosController;
let service: jest.Mocked<ProductosService>;
beforeEach(async () => {
const module = await Test.createTestingModule({
controllers: [ProductosController],
providers: [
{
provide: ProductosService,
useValue: {
findAll: jest.fn().mockResolvedValue({ datos: [], total: 0 }),
findOne: jest.fn(),
create: jest.fn(),
update: jest.fn(),
remove: jest.fn(),
},
},
],
}).compile();
controller = module.get<ProductosController>(ProductosController);
service = module.get(ProductosService);
});
it('findAll debería llamar al servicio con los filtros correctos', async () => {
const filtros = { pagina: 1, limite: 10 };
await controller.findAll(filtros as FiltrosProductoDto);
expect(service.findAll).toHaveBeenCalledWith(filtros);
});
});Tests de guards
// auth/guards/jwt-auth.guard.spec.ts
import { Test } from '@nestjs/testing';
import { JwtAuthGuard } from './jwt-auth.guard';
import { JwtService } from '@nestjs/jwt';
import { Reflector } from '@nestjs/core';
import { ExecutionContext, UnauthorizedException } from '@nestjs/common';
describe('JwtAuthGuard', () => {
let guard: JwtAuthGuard;
let jwtService: jest.Mocked<JwtService>;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
JwtAuthGuard,
{ provide: JwtService, useValue: { verifyAsync: jest.fn() } },
{ provide: Reflector, useValue: { getAllAndOverride: jest.fn() } },
],
}).compile();
guard = module.get<JwtAuthGuard>(JwtAuthGuard);
jwtService = module.get(JwtService);
});
const crearContexto = (token?: string): ExecutionContext => ({
switchToHttp: () => ({
getRequest: () => ({
headers: token ? { authorization: `Bearer ${token}` } : {},
}),
}),
getHandler: jest.fn(),
getClass: jest.fn(),
} as unknown as ExecutionContext);
it('debería lanzar UnauthorizedException sin token', async () => {
const reflector = { getAllAndOverride: jest.fn().mockReturnValue(false) };
const module2 = await Test.createTestingModule({
providers: [
JwtAuthGuard,
{ provide: JwtService, useValue: jwtService },
{ provide: Reflector, useValue: reflector },
],
}).compile();
const guard2 = module2.get<JwtAuthGuard>(JwtAuthGuard);
await expect(guard2.canActivate(crearContexto()))
.rejects
.toThrow(UnauthorizedException);
});
});Cobertura de código
Para generar un reporte de cobertura:
npm run test:covEl objetivo recomendado es 80% de cobertura como mínimo para código de producción. No obsesiones con llegar al 100% —los tests de más valor son los que cubren lógica de negocio compleja, casos límite y flujos de error.
Con una suite de tests robusta, puedes refactorizar y añadir features con confianza. En la lección final, pondrás todo en práctica construyendo una API REST completa de librería con todos los conceptos del curso.
Inicia sesión para guardar tu progreso