Saltar al contenido principal

Convenciones de Testing — Finnova

Este documento define la estrategia y convenciones de testing para el proyecto Finnova. El objetivo inicial es tener cobertura básica en lógica crítica. No se exige cobertura total en MVP.


🧩 Tipos de tests

Unit tests (tests/unit/)

Prueban una función o clase de forma aislada, sin base de datos ni red. Si la función depende de algo externo, se mockea.

Cuándo escribirlos: lógica de negocio en servicios, funciones utilitarias, cálculos, transformaciones de datos.

// auth.service.test.ts
// Testeamos que el servicio hashea la contraseña antes de guardar,
// sin tocar la base de datos real — el repositorio se mockea.
describe('authService.register', () => {
it('debería hashear la contraseña antes de guardarla', async () => {
const mockRepo = { save: jest.fn().mockResolvedValue({ id: '1' }) };
const service = new AuthService(mockRepo);

await service.register({ email: 'test@finnova.com', password: '1234' });

const savedUser = mockRepo.save.mock.calls[0][0];
expect(savedUser.password).not.toBe('1234'); // no guardó en texto plano
expect(savedUser.password).toMatch(/^\$2[ab]\$/); // tiene formato bcrypt
});
});

Tests de integración (tests/integration/)

Prueban que varios componentes funcionan juntos: el endpoint HTTP recibe la request, pasa por el controlador, el servicio y llega a la base de datos real (de test).

Cuándo escribirlos: endpoints de la API, flujos completos de un módulo (registro, login, creación de transacción).

// auth.routes.test.ts
// Levantamos la app completa contra una base de datos de test.
// Verificamos que el endpoint responde correctamente de punta a punta.
describe('POST /auth/register', () => {
it('debería crear un usuario y retornar 201 con token', async () => {
const res = await request(app)
.post('/auth/register')
.send({ email: 'nuevo@finnova.com', password: 'segura123' });

expect(res.status).toBe(201);
expect(res.body).toHaveProperty('token');
expect(res.body.user.email).toBe('nuevo@finnova.com');
});

it('debería retornar 409 si el email ya existe', async () => {
await createUser({ email: 'existe@finnova.com' }); // seed en la db de test

const res = await request(app)
.post('/auth/register')
.send({ email: 'existe@finnova.com', password: 'segura123' });

expect(res.status).toBe(409);
});
});

Tests de componentes UI (tests/components/)

Prueban que un componente de React Native renderiza y responde a interacciones correctamente, sin levantar la app completa.

Cuándo escribirlos: componentes con lógica condicional visible (mostrar/ocultar, estados de carga, mensajes de error).

// LoginForm.test.tsx
// Verificamos que el botón se deshabilita mientras carga
// y que se muestra el error cuando las credenciales son incorrectas.
describe('LoginForm', () => {
it('debería deshabilitar el botón mientras carga', () => {
const { getByText } = render(<LoginForm isLoading={true} onSubmit={jest.fn()} />);
expect(getByText('Ingresar')).toBeDisabled();
});

it('debería mostrar mensaje de error si las credenciales son incorrectas', () => {
const { getByText } = render(
<LoginForm isLoading={false} error="Credenciales inválidas" onSubmit={jest.fn()} />
);
expect(getByText('Credenciales inválidas')).toBeTruthy();
});
});

🎯 Qué testear

PrioridadTipoQuéDónde
AltaUnitServicios del backend (lógica de negocio)tests/unit/
AltaIntegraciónEndpoints de APItests/integration/
MediaUnitFunciones utilitarias en shared/lib/tests/unit/
BajaComponenteComponentes UI con lógica condicionaltests/components/

📁 Estructura de archivos

  • Archivos de test con sufijo .test.ts o .spec.ts.
  • El archivo de test vive junto al archivo que testea, o en la carpeta tests/ replicando la misma estructura de src/.
src/
modules/
auth/
auth.service.ts
auth.service.test.ts ← junto al archivo
tests/
integration/
auth.routes.test.ts ← o en tests/ replicando estructura

✍️ Cómo escribir tests

Nombres

  • Nombres de tests en español, describiendo el comportamiento esperado, no la implementación.
  • Usar describe para agrupar por módulo o función.
// ✅
describe('userService', () => {
it('debería lanzar un error si el email ya existe', async () => { ... });
it('debería retornar el usuario creado con id generado', async () => { ... });
});

// ❌
it('test 1', () => { ... });
it('works', () => { ... });
describe('test suite', () => { ... });

Qué y cómo testear

  • Testear comportamiento observable, no implementación interna.
  • Preferir datos de prueba explícitos sobre mocks masivos — lo que se ve en el test debe ser suficiente para entenderlo sin leer el código fuente.
  • No mockear la base de datos en tests de integración — usar una base de datos de test real para evitar divergencias.
// ✅ Datos explícitos, comportamiento claro
it('debería calcular el saldo total correctamente', () => {
const transactions = [
{ type: 'income', amount: 1000 },
{ type: 'expense', amount: 300 },
];
expect(calculateBalance(transactions)).toBe(700);
});

// ❌ Mock excesivo que oculta el comportamiento
it('calcula saldo', () => {
jest.mock('../transactions');
expect(getBalance()).toBeDefined();
});

🛠️ Herramientas

HerramientaUso
JestFramework principal para unit e integration tests (backend)
SupertestTesting de endpoints HTTP en el backend
React Native Testing LibraryTesting de componentes UI en el frontend

📎 Recursos relacionados