Daniel Murillo
Índice

Arquitectura · 28 de octubre de 2026 · Daniel Murillo

Cómo probar una API: contrato, E2E y carga

La meta no es tener muchas pruebas, sino evidencia rápida de que la API cumple su contrato bajo condiciones relevantes.

La meta no es tener muchas pruebas; es tener evidencia rápida y confiable de que la API cumple su contrato bajo condiciones relevantes.

Introducción

Una API puede devolver 200 OK y seguir siendo incorrecta: datos incompletos, filtrado roto, contrato incompatible o colapso bajo concurrencia.

Las pruebas tienen objetivos distintos. Combina niveles que detecten fallos diferentes con un costo de mantenimiento razonable.

         /\
        /  \     E2E / carga  (pocas, caras, alto valor de integración)
       /----\
      /      \   Contrato / integración
     /--------\
    /          \ Unitarias / lógica  (muchas, rápidas)
   --------------

No hay una pirámide universal. Ajusta según el costo de fallar en producción.

1. Tipos de pruebas

TipoQué verificaLímite típico
UnitariasLógica aisladaNo prueban SQL real ni red
IntegraciónComponentes juntos (API + Postgres)Más lentas; necesitan entorno
ContratoAcuerdo consumidor ↔ proveedorNo prueba toda la lógica de negocio
E2EFlujo completoFrágiles y costosas si se abusa
CargaComportamiento bajo tráficoNo sustituye pruebas funcionales
SeguridadAuthZ, validación, límitesDebe ser deliberada, no un afterthought

2. Ejemplo: Vitest + Supertest

Exporta la app sin abrir el puerto en el mismo módulo del listen.

import request from "supertest";
import { describe, expect, it } from "vitest";
import { app } from "../src/app";

describe("GET /health", () => {
  it("returns a healthy response", async () => {
    const response = await request(app).get("/health");

    expect(response.status).toBe(200);
    expect(response.body).toEqual({ status: "ok" });
  });
});

Esto valida el contrato HTTP de la ruta en proceso, no que Postgres o el despliegue funcionen.

3. Integración con base de datos

Cuando el comportamiento depende de SQL, prueba contra una base real y aislada (p. ej. Testcontainers o un Postgres de CI). Los mocks no verifican índices, constraints ni transacciones.

Buenas prácticas:

  • Migraciones aplicadas en el setup.
  • Datos aislados por prueba o limpieza fiable.
  • Sin compartir DB entre workers paralelos sin aislamiento.
  • Probar errores y restricciones relevantes.

4. Contratos (OpenAPI / Pact)

Un contrato fija rutas, campos, tipos y estados. OpenAPI describe el acuerdo; Pact modela expectativas consumidor-proveedor.

paths:
  /users/{id}:
    get:
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Usuario encontrado
          content:
            application/json:
              schema:
                type: object
                required: [id, email]
                properties:
                  id:
                    type: string
                  email:
                    type: string
                    format: email
        "404":
          description: Usuario no encontrado

Mantén la especificación alineada con la implementación (validación en CI). Un contrato no garantiza la lógica de negocio completa.

5. E2E

Flujos críticos: registro → login → crear recurso → consultarlo. Detectan problemas de secuencia y configuración, pero son lentas. No pongas todas las reglas de negocio solo en E2E.

6. Carga con k6

Define una pregunta: «¿este endpoint cumple p95 bajo X ms con Y RPS y tasa de error bajo Z?»

import http from "k6/http";
import { check, sleep } from "k6";

export const options = {
  vus: 10,
  duration: "30s",
  thresholds: {
    http_req_failed: ["rate<0.01"],
    http_req_duration: ["p(95)<300"],
  },
};

export default function () {
  const response = http.get(`${__ENV.BASE_URL}/health`);
  check(response, { "status is 200": (r) => r.status === 200 });
  sleep(1);
}

/health no representa la carga del negocio. Distingue load, stress, spike y soak. Documenta hardware, datos y perfil de peticiones.

7. Qué observar en carga

  • Throughput y errores
  • Latencia p50 / p95 / p99
  • CPU, memoria, event loop
  • Pool de DB y waits
  • Dependencias externas
  • Colas y reintentos

8. Seguridad y caminos negativos

  • Token ausente, inválido o expirado
  • Usuario sin permiso / IDOR (recurso ajeno)
  • Payloads malformados y campos extra
  • Rate limits y tamaños
  • Idempotencia y reintentos
  • Timeouts de dependencias
  • Datos sensibles en respuestas y logs

9. Herramientas

HerramientaUso
Vitest / JestUnitarias e integración ligera
SupertestHTTP contra la app Node
PlaywrightE2E de navegador
PactContrato consumidor-proveedor
OpenAPI toolingContrato HTTP
k6Carga
TestcontainersDependencias reales en CI
OWASP ZAPSeguridad dinámica (con cuidado)

10. CI/CD

  1. Lint y tipos
  2. Unitarias rápidas
  3. Integración con deps aisladas
  4. Contratos
  5. Build
  6. E2E en entorno controlado
  7. Carga como gate aparte en cambios críticos

No todo en cada commit: feedback rápido primero; lo caro cuando aporta valor.

Conclusión

Las pruebas no son una competencia por cantidad de casos. Son una estrategia para detectar fallos en el nivel útil al costo adecuado.

Unitarias para lógica, integración para colaboraciones reales, contratos para acuerdos, E2E para recorridos críticos y carga para capacidad declarada.

En esta serie

  1. Autenticación
  2. Observabilidad
  3. SQL, query builders y ORMs
  4. Cómo probar una API (esta nota)
  5. Contenedores y Twelve-Factor

Lecturas relacionadas

Referencias