Guía de Testing
Cómo estructurar una estrategia de pruebas completa en Fox Framework aprovechando las utilidades descritas en docs/testing.mdx.
Contenido
- Estrategia General
- Configuración Base
- Tests Unitarios
- Tests de Integración
- End-to-End (E2E)
- Mocks y Fixtures
- Cobertura y Calidad
- Performance Testing
Estrategia General
Pilares:
- Pirámide balanceada: 70% unit, 20% integración, 10% e2e
- Tests deterministas y paralelizables
- Datos de prueba aislados en cada suite
Configuración Base
Instala dependencias (si no están):
npm i -D jest ts-jest @types/jest supertestjest.config.js básico:
module.exports = { preset: 'ts-jest', testEnvironment: 'node', roots: ['<rootDir>/src','<rootDir>/tests'] };Setup global:
// tests/setup.ts
beforeAll(async ()=>{/* init db/cache mock */});
afterAll(async ()=>{/* close resources */});Tests Unitarios
Características:
- Sin IO real (DB, red) -> usar mocks
- Ejecutan en <100ms
Ejemplo controlador:
describe('UserController', ()=> {
it('devuelve 404 si no existe', async ()=> {
const repo = { findById: jest.fn().mockResolvedValue(null) };
const controller = new UserController(repo as any);
const req: any = { params:{ id:'x' } }; const res: any = mockRes();
await controller.get(req,res);
expect(res.status).toHaveBeenCalledWith(404);
});
});Mock response helper:
function mockRes(){
const res: any = {};
res.status = jest.fn().mockReturnValue(res);
res.json = jest.fn().mockReturnValue(res);
return res;
}Tests de Integración
Objetivo: múltiples capas reales (routing + middleware + parte de servicio) con DB ligera (SQLite in-memory) o contenedores temporales.
import request from 'supertest';
let app;
beforeAll(async ()=> { app = await createAppTestInstance(); });
test('GET /api/users/:id', async ()=> {
const user = await seedUser();
const r = await request(app).get(`/api/users/${user.id}`).expect(200);
expect(r.body.data.id).toBe(user.id);
});End-to-End (E2E)
Cubre flujos completos (registro -> login -> recurso protegido):
it('flujo auth', async ()=> {
await request(app).post('/auth/register').send({ email:'a@b.c', password:'x' }).expect(201);
const login = await request(app).post('/auth/login').send({ email:'a@b.c', password:'x' }).expect(200);
const token = login.body.token;
await request(app).get('/profile').set('Authorization',`Bearer ${token}`).expect(200);
});Minimiza e2e (solo caminos críticos) y ejecuta en paralelo si es posible.
Mocks y Fixtures
Factory pattern:
export function userFactory(overrides: Partial<User> = {}): User {
return { id: crypto.randomUUID(), email: `u${Date.now()}@t.dev`, name: 'Test', ...overrides };
}Fixtures estáticos para roles/permisos predecibles.
Cobertura y Calidad
Scripts:
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
}Reglas recomendadas:
- Cobertura global >= 80%
- Sin
anyno justificado en código probado - Fallar build si coverage desciende (>2% respecto a main)
Performance Testing
Ejemplo carga simple:
import autocannon from 'autocannon';
test('lista usuarios bajo carga', (done)=> {
const instance = autocannon({ url: 'http://localhost:3000/api/users', connections: 50, duration: 10 });
instance.on('done', r => { expect(r.errors).toBe(0); done(); });
});Métricas a observar: p95, throughput, error rate.
Mejores Prácticas
- Nombrar tests descriptivamente
- AAA (Arrange, Act, Assert)
- Un assert conceptual principal por test (múltiples secundarios permitidos)
- Evitar mocks profundos: refactoriza para aislar
- Reutiliza helpers y factories