Mejores Prompts para Testear APIs REST con IA: Supertest, MSW y Contratos
12 prompts para generar tests de API REST con IA: Supertest, tests de contrato, validación de schemas y casos límite. Copia y pega.
Tabla de contenidos
Escribes la ruta, la haces funcionar en local con un curl, das por bueno el día y te vas a casa. Dos semanas después un cliente reporta que al crear un pedido con el campo discount a null el backend devuelve un 500 con la conexión al pool de PostgreSQL ya agotada. El problema no era la lógica de negocio: era que ese endpoint nunca tuvo un solo test.
El caso de las APIs es el más traicionero de todos. El código se ejecuta, la respuesta es JSON válido, Postman devuelve 200 y todo parece correcto. Pero detrás hay veinte caminos: los cuatro estados posibles de autenticación, los seis errores de validación, la paginación en sus tres bordes, el rate limit que nadie prueba y el idempotency key que se ignora silenciosamente. Escribir esos veinte tests a mano es un día entero de trabajo mecánico. Escribirlos bien, con los casos límite que de verdad importan, es un problema distinto.
Por eso los prompts de esta guía no son del tipo “escríbeme tests para mi API”. Son prompts que obligan a la IA a trabajar como alguien que ha leido tu contrato, tu esquema de base de datos y tu middleware de auth. Cada bloque va seguido de por qué funciona, que es la parte que de verdad importa: si entiendes por qué un prompt produce buenos tests, puedes adaptarlo a tu código sin copiar y pegar a ciegas.
Si antes de esto quieres tener la base sólida, echa un vistazo a cómo testear código generado por IA con Copilot y Cursor, que cubre el flujo completo de verificación. Y si tu API es Express o NestJS con JWT, mi guía para proteger una API Node.js con JWT tiene el código de referencia que luego aquí vamos a testear.
Cinco reglas de oro antes de copiar nada
1. Pega el contrato real, no una descripción. Si tienes un openapi.yaml, pégalo. Si no, pega el controller, el schema de Zod y la definición del modelo. La calidad del prompt sube en proporción directa a la cantidad de código real que le des. Es la misma regla que sigue mi guide de prompts para programar: contexto específico, resultados utilizables.
2. Pide siempre los casos límite, no solo el happy path. El 80% de los tests generados sin instrucción explícita son el happy path. Añade literalmente la frase “incluye casos límite y de error” al final del prompt y verás cómo la cobertura de la suite salta.
3. Un endpoint por prompt. Si le pides toda la API de golpe, te devuelve un test genérico y repetitivo. Si le pides un endpoint, con sus cuatro variables de estado, te devuelve algo que de verdad protege el código.
4. Nombra los tests por el comportamiento, no por la función. describe('POST /orders') no dice nada. describe('POST /orders con token expirado') te dice qué se rompe cuando el test falla a las 3 de la mañana.
5. Revisa siempre la primera generación. Los tests generados por IA tienen un sesgo hacia lo plausible, no hacia lo correcto. Si un test pasa y nunca falló, probablemente no está probando nada. Activa --reporter=verbose y mira si algún test te sorprende.
1. Esqueleto base con Supertest y Vitest
Estoy preparando la suite de tests de una API REST en Node.js con Express.
Stack de testing: Vitest + Supertest + Zod + Prisma.
Runner: vitest con configuración de isolate y globals en false.
Objetivo de este prompt: genera el esqueleto de infraestructura de tests,
no los tests en sí.
Necesito:
1. Un helper `createTestApp()` en [ruta del proyecto, ej: tests/helpers/app.ts]
que importe la app real de [ruta, ej: src/app.ts] SIN levantarla en un puerto
real (patrón supertest(app) sobre la instancia, no app.listen).
2. Un helper `seed()` y `clear()` sobre la base de datos de test,
con una tabla separada por entorno (DATABASE_URL_TEST).
3. Un helper `authHeader()` que firme un JWT válido para un usuario semilla
usando la misma clave y el mismo payload que usa src/middleware/auth.ts.
4. Un helper `invalidAuthHeader()` que produzca un token expirado, uno con
firma inválida y uno ausente.
5. Configuración de vitest.config.ts con environment node, setupFiles
y un globalSetup que ejecute las migraciones de Prisma sobre la BD de test
antes de empezar.
El helper authHeader debe reusar la función de firma real del proyecto,
no reimplementar jwt.sign a mano. Si no existe esa función,
dímelo en vez de inventarla.
Dame los archivos completos con sus imports.
Por qué funciona: el bootstrap de una suite de tests es la parte más aburrida y más mecánica. Si lo escribes tú, te lleva una hora y lo haces mal la primera vez. Pedirlo explícitamente como bloque separado evita que la IA mezcle infraestructura con aserciones.
Ejemplo real de lo que devuelve el helper de auth:
import jwt from 'jsonwebtoken';
import { config } from '../../src/config';
import type { AuthUser } from '../../src/types/auth';
export function authHeader(user: AuthUser = seedUser) {
const token = jwt.sign(
{ sub: user.id, email: user.email, role: user.role },
config.jwt.secret,
{ expiresIn: config.jwt.expiresIn }
);
return `Bearer ${token}`;
}
export const expiredToken = jwt.sign(
{ sub: seedUser.id, email: seedUser.email, role: seedUser.role },
config.jwt.secret,
{ expiresIn: '-1s' }
);
export const forgedToken = jwt.sign(
{ sub: seedUser.id, role: 'admin' },
'clave-del-atacante'
);
2. Matriz de códigos de estado de un endpoint
Voy a testear el endpoint [POST /ruta] de mi API Express.
Implementación actual:
[pegar el controller completo]
Contrato esperado según OpenAPI:
[pegar el fragmento del YAML: requestBody, responses, security]
Validación de entrada: [pegar el schema Zod]
Autenticación: [JWT Bearer, campo opcional/protegido]
Rol requerido: [admin | usuario | ninguno]
Genera una matriz de tests con Vitest + Supertest donde cada fila cubre
un escenario distinto. Como mínimo:
Happy path (201/200) con payload válido mínimo
Payload válido completo
Falta el header Authorization (401)
Token expirado (401)
Token con firma inválida (401)
Autenticado pero rol insuficiente (403)
Recurso inexistente (404)
Body no es JSON válido (400)
Falta un campo obligatorio (422 según mi convención)
Campo con tipo incorrecto (422)
Campo fuera de rango o con valor límite (422)
Body vacío (400 o 422, el que corresponda a mi convención)
Para cada test:
- Nombre descriptivo del comportamiento, no del método
- El status esperado
- Una aserción sobre la forma del body de error usando el mismo
contrato de error que devuelve mi código (no inventes otro formato)
- Un comentario breve explicando qué lógica cubre
Devuélveme solo el array de tests, sin repetir imports.
Por qué funciona: enumerar los escenarios tú mismo en el prompt es lo que separa un test útil de una cobertura decorativa. La IA es capaz de detectar casos raros si le dices que los busque; si no se los dices, se queda en el camino feliz.
3. Tests de contrato contra el spec OpenAPI
Tengo una API REST en Express con un fichero OpenAPI en
[ruta, ej: docs/openapi.yaml] y código generado de Zod desde el spec.
Quiero tests de contrato que comprueben que mi implementación
CUMPLE lo que el spec declara, no que hace lo que yo creo que hace.
Para el endpoint [MÉTODO /ruta]:
1. Extrae del spec: parámetros, requestBody, todas las respuestas
declaradas (incluidas las 4xx y 5xx), headers requeridos y
requisitos de seguridad.
2. Genera un test por cada respuesta declarada en el spec, asserting
el status code exacto y que el body cumple el schema Zod correspondiente.
3. Marca con it.todo cualquier respuesta que el spec declara pero tu
análisis dice que la implementación NO cubre. Esto último es
importante: quiero ver los huecos.
4. Añade un test que compare dinámicamente el status de cada respuesta
real contra el status documentado, y falle si hay respuestas no
documentadas.
Usa Ajv o el validador que ya tengas. No hardcodes los campos:
léelos del spec si puedes.
Por qué funciona: los tests de contrato detectan el caso más caro de todos: el endpoint que funciona pero que nunca documentaste, o que documentaste con un status distinto al que devuelve. El paso 3 es clave, porque convierte el gap entre spec e implementación en output visible en el reporte de test.
Ejemplo real de un gap detectado así:
✗ it.todo → POST /orders debe devolver 409 cuando el idempotency key
ya existe con un body diferente (está en el spec, el
controller devuelve 500)
4. Validación de schemas con Zod: tabla válida/inválida
Quiero tests de validación de entrada usando Zod en mi API Express.
Schema a testear:
[pegar el schema Zod completo]
Para cada campo del schema genera una fila de test con dos casos:
payload VÁLIDO (debe pasar) y payload INVÁLIDO (debe fallar con 422).
Presta atención especial a:
- Strings: string vacío, solo espacios, longitud mínima, longitud máxima,
formato email/url, caracteres de control, null cuando es required
- Números: 0, negativos, NaN, Infinity, valor máximo del dominio,
float donde se espera entero
- Arrays: vacío, con null dentro, con el doble de elementos del max
- Enums: valor no listado, valor con mayúscula distinta
- Nested: objeto anidado a null, clave anidada ausente
- Fechas: string de fecha inválida, fecha futura cuando se exige pasada
Para cada caso inválido, además del status, assert sobre el path
del error (p.ej. ['body', 'items', 0, 'sku']) y sobre el código de
error específico de tu convención de validación.
Devuelve un array de tests Vitest con describe.each cuando el número
de filas lo justifique.
Por qué funciona: Zod genera el mensaje de error con una estructura (error.issues[] con path y code). Aserar sobre path y code en vez de sobre el string del mensaje hace el test estable frente a cambios de redacción, que es donde estos tests se rompen sin necesidad.
5. Tests de idempotencia y reintentos
Mi API expone [POST /ruta] que crea un recurso y es idempotente
mediante una cabecera Idempotency-Key. Implementación:
[pegar el middleware y el fragmento del controller que lo usa]
Genera los tests que un cliente real dispara cuando la red falla:
1. Misma Idempotency-Key con el MISMO body, dos veces seguidas
→ mismo ID de recurso, un solo registro, status idéntico
2. Misma Idempotency-Key con body DIFERENTE
→ mi convención es devolver [409 | 422]. Assert de ese status.
3. Petición simultánea con la misma key (concurrentes)
→ solo se crea un recurso
4. Sin Idempotency-Key
→ se comporta como una creación normal
5. Idempotency-Key vacía o con longitud absurda (>255 chars)
→ [status según tu convención]
6. La key se guarda con TTL de [24h]. Testea, con el reloj mockeado
(vi.useFakeTimers), que pasada la ventana se puede reutilizar la key.
Para el caso 3 necesito que el test sea determinista: usa
Promise.all sobre N peticiones paralelas, no temporizadores.
Explica cómo has simulado el fallo de red intermedio, porque en
producción esto pasa por timeouts y no por errores 500 limpios.
Por qué funciona: los reintentos del cliente son el origen más silencioso de duplicados en producción. El caso 3 (concurrencia) casi nunca está cubierto en suites reales, y es exactamente el que falla cuando subes de un nodo a varios.
6. Paginación, filtros y ordenación
Testea el endpoint de listado [GET /ruta].
Implementación actual (controller + query builder Prisma):
[pegar código]
La API documenta estos query params:
[pegar la sección del spec o describir: limit, cursor, offset, sortBy, order, filtros]
Genera los tests de paginación con los bordes reales, no solo el caso fácil:
- Sin parámetros: devuelve la primera página con el total o el hasMore
que ya uso (assert del campo exacto)
- limit=0 → [mi convención: 400 con mensaje específico]
- limit=-5
- limit mayor que el máximo permitido (assert del clamp o del error)
- limit exactamente en el máximo
- offset mayor que el total de registros → lista vacía, NO error
- offset + limit en el último elemento exacto → exactamente 1 elemento
- sortBy por un campo que no existe
- order=ASC y order=desc sobre un campo numérico: assert de que la
secuencia es efectivamente monótona, no solo de longitud
- combinación de filtro + orden + paginación a la vez
- filtro por valor que no existe en ningún registro → vacío
- filtro por texto con caracteres especiales y con % y _ si uso LIKE
Para el caso de orden, no te limites a assert de longitud:
compara el array de IDs completo contra el orden esperado calculado.
Por qué funciona: los bugs de paginación casi nunca están en el limit, están en el borde. offset mayor que el total, limit=0, y el ORDER BY que en algunos motores no es estable cuando hay empate, son los tres clásicos.
7. Rate limiting
Mi API aplica rate limiting con express-rate-limit en [ruta/middleware].
Configuración actual: [pegar config]
Genera los tests del rate limiter usando los datos del test, sin esperar
en tiempo real. Dos estrategias, dime cuál aplicas y por qué:
1. Con el reloj falso de Vitest (vi.useFakeTimers) avanzando el tiempo
por ventana.
2. Con una store en memoria inyectada y cleanup entre tests.
Casos que necesito cubiertos:
- N peticiones dentro de la ventana → todas 200
- petición N+1 dentro de la ventana → 429 con mi formato de error
- el header X-RateLimit-Remaining decrece correctamente
- pasado el TTL de la ventana, vuelve a 200
- el Identify se extrae de la API key del usuario, no solo de la IP,
y dos usuarios distintos no comparten cuota
- el endpoint /health no está sujeto al límite
No uses setTimeout real ni esperes segundos en el test.
Si mi configuración usa un store externo (Redis), indícalo como
limitación del entorno de test y propón una alternativa viable en CI.
Por qué funciona: los tests de rate limit con temporizadores reales son la causa número uno de suites que tardan 90 segundos en correr. La clave está en agrupar las peticiones con fake timers para que la lógica se pruebe sin esperar.
8. Factories y datos de test realistas
Voy a crear factories de datos de test para mi API con Prisma y Vitest.
Modelos Prisma relevantes:
[pegar el schema.prisma: los modelos User, Order, Product y sus relaciones]
Genera un módulo tests/factories.ts que use faker con seed fija
(faker.seed(42), para que los datos sean reproducibles) y exporte:
- createUser(overrides?)
- createAdminUser(overrides?)
- createProduct(overrides?)
- createOrder(user, overrides?)
Requisitos:
- Cada factory sobrescribe el email con un único valor (faker.string.uuid)
para no violar el índice único
- createOrder calcula un total coherente con los items que genera
- Devuelve el objeto plano serializable, sin la Dates de Prisma sin
convertir, para poder comparar en los asserts
- Que createOrder cree los items anidados de verdad (include) y devuelva
la orden con el include, para que los asserts tengan los datos
Nada de helpers genéricos tipo create('User') al estilo factory-boy:
prefiero funciones simples y tipadas con el tipo de retorno explícito.
Por qué funciona: las factories tipadas evitan que los tests compartan un único fixture mutable, que es la causa más común de tests que fallan al ejecutarse en paralelo. Además, pedir tipos de retorno explícitos evita los any que luego rompen el type-check del proyecto.
9. Mockear servicios externos con MSW
Mi API Express llama a servicios externos que no quiero llamar en test:
- Stripe para pagos (endpoint [ruta de checkout])
- Un servicio de email en [ruta]
- [otro servicio externo]
Genera los handlers de MSW (msw v2, sintaxis http.get/http.post) para
simular: éxito, error 4xx y error 5xx de cada servicio.
Además, el punto clave: muéstrame cómo sobreescribir un handler concreto
en un test concreto (server.use(...)), cómo resetear los handlers entre
tests (afterEach) y cómo assertar el request que el servidor recibió
(vi.fn handler o el recorder de msw) para verificar que mi código envía
los campos correctos.
Mi código actual llama al servicio así:
[pegar el módulo de integración con Stripe]
Casos a cubrir:
- Respuesta OK: assert del status de mi endpoint y de que se persiste
el payment intent id que devolvió el mock
- Timeout del externo: assert del status de mi endpoint y del mensaje
de error. [mi convención: timeout → 502]
- El externo devuelve un body con shape inesperado: ¿mi código valida
la respuesta antes de usarla? Si no, dímelo y propón cómo sí.
Por qué funciona: MSW v2 intercepta a nivel de red, así que el código de producción no necesita condicionales if (isTest). Eso mantiene el test cerca de la ejecución real y evita el clásico “en test funciona porque mockeamos el módulo entero”.
10. Tests de flujo completo con dos APIs
Quiero un test de integración que recorra un flujo real de mi API:
[registro → verificación de email → login → crear recurso → listar →
editar → borrar → verificar que desaparece]
Implementación de las rutas y el flujo de auth:
[pegar los controllers relevantes]
Genera UN describe con los tests en orden, sharing state mediante
variables del closure (el usuario creado en el test 1 se reutiliza
en el 5), con beforeAll para el seed y afterAll para el cleanup.
Reglas:
- Nada de beforeEach que resetee la BD entre steps del mismo flujo
- Cada step debe assertar explícitamente el status y guardar en una
variable lo que hace falta para el siguiente step
- Si un step puede fallar y dejar el flujo inconsistente, aísla con
un try/finally que llame a la limpieza
- Al final, assert que la base de datos está en el estado esperado
(por ejemplo, recuento de filas)
Marca con test.fails() los steps que sé que están rotos y explícame por qué.
Por qué funciona: los tests de flujo son los que más valor tienen para detectar regresiones de negocio, pero también los que más se rompen por acoplamiento. Pedir explícitamente que marque los conocidos rotos con test.fails() convierte un test rojo permanente en documentación viva de un bug.
11. Buscar los huecos de cobertura
Tengo esta suite de tests de API:
[pegar los archivos de test o un resumen de los endpoints y los tests]
Ejecuta mentalmente una revisión de cobertura por endpoint y dime:
1. Qué endpoint tiene MENOS scenarios de entre los declarados
(happy path / sin auth / rol insuficiente / body inválido /
no encontrado / dependencies caídas)
2. Qué campos del schema Zod no aparecen en ningún payload de test
3. Qué códigos de error de mi enum de errores no se comprueban nunca
4. Qué queries Prisma no se ejercitan con datos en el borde
Para cada hueco, escribe el test exacto que lo cubre.
No me des porcentajes de cobertura: dame los huecos concretos.
Después, si tu respuesta es que la cobertura numérica engaña,
explícame por qué un endpoint puede tener 95% de cobertura de líneas
y ningún test que detecte un bug de negocio.
Por qué funciona: la cobertura de líneas mide qué código se ejecutó, no qué comportamiento se verificó. Una suite al 100% que solo hace expect(res.status).toBe(200) sobre todo no detecta ni un solo bug de lógica.
12. Test de regresión a partir de un bug real
Este bug está en producción y ya lo he arreglado:
Síntoma: [descripción exacta del fallo, con mensaje de error o log]
Entorno: [versión de Node, base de datos, si ocurre solo en prod]
Causa raíz: [si ya la conoces, o "no la conozco aún"]
Mi arreglo: [pegar el diff o el código nuevo]
Genera un test de regresión que:
1. FALLARÍA con el código anterior. Si no puedes garantizar eso,
dilo explícitamente y explica por qué no puedes garantizarlo.
2. Reproduce las condiciones exactas del fallo:
[volumen de datos, estado previo de la BD, cabeceras, momento]
3. Falla de forma clara y legible: el mensaje del assert debe describir
el síntoma del bug, no la estructura interna.
Si el bug solo aparece con volumen o concurrencia, dime cómo
aproximarlo en un test de integración de 200ms en lugar de esperar
a que ocurra en staging.
Por qué funciona: el test de regresión es el único prompt de la lista que tiene un criterio de éxito objective: tiene que fallar contra el código viejo. Si la IA no puede garantizarlo, te está diciendo que el test es decorativo.
Anti-patrones: lo que veo en casi todas las suites generadas
❌ Mockear tu propia base de datos en un test de endpoint. Estás testeando el mock, no la API. Y el fallo real (una consulta Prisma mal formada) nunca aparece porque el mock devuelve lo que le pediste.
// ❌ Inútil: todo pasa siempre
vi.mock('../src/db', () => ({
prisma: { order: { findMany: vi.fn().mockResolvedValue([]) } }
}));
it('lista pedidos', async () => {
await request(app).get('/orders').expect(200);
});
✅ Base de datos real de test con seed controlado, y asserts sobre la respuesta y sobre el estado final.
// ✅ Prueba comportamiento real
it('lista solo los pedidos del usuario autenticado', async () => {
const userA = await createUser();
await createOrder(userA);
const userB = await createUser();
await createOrder(userB);
const res = await request(app)
.get('/orders')
.set('Authorization', authHeader(userA));
expect(res.status).toBe(200);
expect(res.body.data).toHaveLength(1);
expect(res.body.data[0].userId).toBe(userA.id);
});
❌ Asertar sobre el string completo del mensaje de error. Cualquier refactor de redacción del mensaje rompe veinte tests sin que haya cambiado un solo comportamiento.
// ❌ Frágil
expect(res.body.message).toBe('The field email is required and must be a string');
✅ Asertar sobre la estructura del error: path del campo y código.
// ✅ Estable
expect(res.status).toBe(422);
expect(res.body.errors).toEqual(
expect.arrayContaining([
expect.objectContaining({ path: ['email'], code: 'invalid_type' })
])
);
❌ Un it gigante de 80 líneas que cubre seis comportamientos. Cuando falla, el mensaje no dice qué se rompió y el siguiente que lo toca no sabe qué puede tocar.
✅ Un comportamiento por it, nombre en formato Given/When/Then o en lenguaje de negocio.
❌ Cadenas de expect sobre la respuesta sin assert del side effect. Tu endpoint devuelve 200 pero no ha guardado nada en la base de datos. El test pasa y el bug está vivo.
✅ Asertar la respuesta y el efecto secundario, siempre que exista.
Catálogo rápido: qué librería para qué capa
| Capa | Herramienta en 2026 | Qué cubre | Qué no cubre |
|---|---|---|---|
| Endpoint HTTP | Vitest + Supertest | Routing, middlewares, auth, códigos de estado | El navegador y el DOM |
| Esquema de entrada | Zod + el propio test unitario | Tipos, formatos, rangos, mensajes | Que el schema esté en la ruta correcta |
| Integridad de datos | Prisma + BD de test | Queries, constraints, transacciones | Lógica de negocio |
| Servicios externos | MSW v2 | Timeouts, 4xx/5xx del tercero, forma del body | Que el tercero real funcione igual |
| Contrato | OpenAPI + Ajv | Que la implementación cumpla el spec | Casos de uso no documentados |
| E2E desde el navegador | Playwright | Flujo real completo | Cosas que no se ven en pantalla |
Si quieres ver la otra mitad del problema, tienes los prompts para generar tests E2E con Playwright y los prompts para testear componentes React. Y para conectar los tests de auth con el resto de la estrategia, mira los prompts para depurar código con IA.
Errores frecuentes y cómo salir de ellos
La IA genera jest.fn() en un proyecto con Vitest. Pide explícitamente “usa vi.fn() y vi.mock() de Vitest, no API de Jest” en el primer prompt y no vuelve a pasar.
Los tests pasan contra la base de datos de desarrollo. Si no defines DATABASE_URL_TEST separado, estás a un prisma db push de cargarte los datos reales. Pon la variable en el helper de bootstrap y verifica que apunta al esquema _test.
La suite tarda 40 segundos porque hay await new Promise(r => setTimeout(r, 1000)) en cada test. Todos esos sleep son síntomas de tests que esperan condiciones de carrera reales. Sustitúyelos por polling con timeout explícito o por vi.waitFor.
Los tests son verdes pero en CI fallan uno de cada diez. Casi siempre es test pollution por orden de ejecución. Ejecuta con --sequence.shuffle un par de veces: si falla, ya sabes que hay estado compartido.
Aparecen errores de Prisma con “too many connections”. El pool se agota porque cada test abre su propia conexión y nunca se cierra. Exporta un afterAll que llame a prisma.$disconnect() y limita el pool a 2 conexiones en la URL de test.
La IA se inventa endpoints que no existen. Le pasa más con APIs grandes. Pásale el resultado de grep -r "router\." src/routes o la salida del spec, nunca una descripción en prosa.
El flujo completo
Contrato (OpenAPI / schema Zod)
│
▼
┌──────────────────────┐
│ Prompt 3 → validación│ Tabla válido/inválido por campo
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ Prompt 2 → matriz de │ Status codes: 200/201/401/403/404/422
│ códigos de estado │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ Prompt 8 → factories │ Seed reproducible con faker
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ Prompt 1 → bootstrap │ app real + BD de test + JWT reales
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ Prompt 9 → MSW │ Servicios externos y sus fallos
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ Prompt 5/6/7 → bordes│ Idempotencia, paginación, rate limit
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ Prompt 10 → flujo E2E│ Camino completo de negocio
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ Prompt 11 → huecos │ Lo que no cubre nada todavía
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ Prompt 12 → regresión│ Un test por cada bug real corregido
└──────────────────────┘
Conclusión
La diferencia entre una suite generada por IA que parece útil y una que de verdad protege tu API está en una sola cosa: cuánto contexto real le das y cuánto le obligas a pensar en el camino que no debería pasar. Con el controller, el schema y el spec delante, los prompts de esta guía te ahorran horas. Sin eso, te dan un expect(200) y la sensación de que tienes tests.
Y un apunte que no debe quedarse en el tutorial: los tests de API no detectan vulnerabilidades. Son tests de comportamiento, no de seguridad. Para eso tienes los prompts de auditoría de seguridad con IA, con su sección honesta sobre lo que ningún modelo detecta. Si además vas a meter Prisma en el proyecto, mi tutorial de Prisma desde cero tiene el setup de la base de datos de test resuelto, y para webhooks que merecen tests de contrato de verdad, mira la implementación de webhooks de Stripe en Next.js.
Si prefieres no escribir estos prompts a mano, mi generador de prompts interactivo te los monta según la tarea y el lenguaje. Y si quieres entender mejor por qué funciona este enfoque antes de aplicarlo, empieza por programar con IA sin arruinarte.