Guía de Testing en NestJS
En NestJS, el testing es super importante para asegurar la calidad y estabilidad de tus aplicaciones. Existen dos tipos principales de pruebas:
- Pruebas unitarias: Verifican el funcionamiento de unidades individuales de código (funciones, métodos, servicios).
- Pruebas de integración: Evalúan cómo interactúan entre sí los distintos componentes de la aplicación (controladores, servicios, base de datos).
NestJS utiliza Jest como framework de testing por defecto, y para pruebas de integración HTTP se recomienda Supertest.
¿Por qué hacer testing?
Las pruebas permiten detectar errores antes de que lleguen a producción, facilitan el mantenimiento y la evolución del código, y ayudan a documentar el comportamiento esperado de la aplicación.
Métricas de cobertura
Cuando ejecutas tus pruebas, obtienes métricas como:
- % Stmts (Statements): Porcentaje de declaraciones ejecutadas.
- % Branch (Branches): Porcentaje de rutas lógicas (if, else, switch) cubiertas.
- % Funcs (Functions): Porcentaje de funciones/métodos llamados.
- % Lines: Porcentaje de líneas ejecutables cubiertas.
Un bajo porcentaje indica que hay partes del código sin probar, lo que puede llevar a errores no detectados.
1. Pruebas Unitarias en NestJS
Las pruebas unitarias se centran en componentes individuales, como servicios o controladores. El objetivo es aislar la unidad bajo prueba, simulando sus dependencias.
Primeros pasos
Ejecuta el siguiente comando para ver la cobertura actual de tu proyecto, puedes basarte en este repositorio de ejemplo:
npm run test:cov
Observa el reporte y analiza qué archivos tienen baja cobertura.
Ejemplo básico: Servicio de Usuarios
Supón que tienes el servicio src/users/users.service.ts. Crea el archivo de pruebas users.service.spec.ts en la misma carpeta.
Paso 1: Configuración inicial
// users.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';
import { RolesService } from '../roles/roles.service';
import { getRepositoryToken } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { AppLogger } from '../../common/logger/logger.service';
const mockRepository = {
find: jest.fn(),
findOne: jest.fn(),
save: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
};
const mockRolesService = { findByName: jest.fn() };
const mockLogger = { debug: jest.fn(), log: jest.fn(), error: jest.fn(), warn: jest.fn() };
describe('UsersService', () => {
let service: UsersService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UsersService,
{ provide: RolesService, useValue: mockRolesService },
{ provide: getRepositoryToken(User), useValue: mockRepository },
{ provide: AppLogger, useValue: mockLogger },
],
}).compile();
service = module.get<UsersService>(UsersService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
Tip: Ejecuta este test y verifica que el entorno está correctamente configurado antes de avanzar.
Paso 2: Prueba de creación de usuario
Agrega una prueba para el método create:
it('should create a user successfully when role exists', async () => {
const createUserDto = { username: 'testuser', email: 'test@example.com', passwordHash: 'hashedpassword', bio: 'Test bio', roleName: 'admin' };
const mockRole = { id: 1, name: 'admin', description: 'Administrator role' };
const mockCreatedUser = { id: 1, ...createUserDto, role: mockRole };
const mockSavedUser = { id: 1, username: 'testuser', email: 'test@example.com', passwordHash: 'hashedpassword', bio: 'Test bio', role: mockRole };
mockRolesService.findByName.mockResolvedValue(mockRole);
mockRepository.create = jest.fn().mockReturnValue(mockCreatedUser);
mockRepository.save.mockResolvedValue(mockSavedUser);
const result = await service.create(createUserDto);
expect(mockRolesService.findByName).toHaveBeenCalledWith('admin');
expect(mockRepository.create).toHaveBeenCalledWith({ ...createUserDto, role: mockRole });
expect(mockRepository.save).toHaveBeenCalledWith(mockCreatedUser);
expect(result).toEqual(mockSavedUser);
});
Ejercicio: Ejecuta la prueba y verifica que pase. Modifica el DTO y observa cómo cambian los resultados.
Paso 3: Prueba de consulta de usuarios
it('should return an array of users', async () => {
const mockUsers = [
{ id: 1, username: 'user1', email: 'user1@example.com', bio: 'Bio 1' },
{ id: 2, username: 'user2', email: 'user2@example.com', bio: 'Bio 2' },
];
mockRepository.find.mockResolvedValue(mockUsers);
const result = await service.findAll();
expect(mockRepository.find).toHaveBeenCalled();
expect(result).toEqual(mockUsers);
});
Recomendación: Agrega pruebas para los métodos
findOne,updateyremovesiguiendo el mismo patrón.
2. Pruebas de Integración (E2E) en NestJS
Las pruebas de integración verifican que los módulos funcionen correctamente juntos, incluyendo rutas HTTP y acceso a la base de datos.
Estructura recomendada
Organiza tus pruebas E2E en la carpeta test/ en la raíz del proyecto:
proyecto/
├── src/
├── test/
│ ├── app.e2e-spec.ts
│ ├── users.e2e-spec.ts
│ ├── helpers/
│ └── fixtures/
Instalación de Supertest
npm install --save-dev supertest
Ejemplo básico: Prueba E2E de usuarios
Paso 1: Configuración y login
import { INestApplication } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { AppModule } from '../src/app.module';
import * as request from 'supertest';
describe('Users E2E', () => {
let app: INestApplication;
let adminToken: string;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
// Login y obtención de token
const response = await request(app.getHttpServer())
.post('/auth/login')
.send({ email: 'admin@mail.com', password: 'password123' })
.expect(201);
adminToken = response.body.access_token;
});
afterAll(async () => {
await app.close();
});
Paso 2: Crear usuario vía API
it('should create a new user', async () => {
const createUserDto = {
username: 'newuser',
email: 'newuser@test.com',
passwordHash: 'password123',
bio: 'New test user',
roleName: 'admin',
};
const response = await request(app.getHttpServer())
.post('/users')
.set('Authorization', `Bearer ${adminToken}`)
.send(createUserDto)
.expect(201);
expect(response.body).toHaveProperty('id');
expect(response.body.username).toBe(createUserDto.username);
});
Paso 3: Consultar usuarios
it('should return all users', async () => {
const response = await request(app.getHttpServer())
.get('/users')
.set('Authorization', `Bearer ${adminToken}`)
.expect(200);
expect(Array.isArray(response.body)).toBe(true);
expect(response.body.length).toBeGreaterThan(0);
});
});
Sugerencia: Agrega pruebas para consultar por ID, actualizar y eliminar usuarios.