¿Qué es el contract testing de APIs, y cómo encaja una especificación OpenAPI/Swagger en tu flujo de desarrollo y pruebas?
Las pruebas de integración detectan el desvío solo cuando dos equipos chocan en un entorno compartido — el contract testing lo detecta en el CI de cada equipo, contra una especificación que ambos ya acordaron.
APRENDE HACIENDOPon a prueba tu intuición3 pasos
¿Qué es el contract testing de APIs, y cómo encaja una especificación OpenAPI/Swagger en tu flujo de desarrollo y pruebas?
Usa los ejemplos y la explicación de la lección para comprobar tu idea.
Reflexión personal. No se envía a ningún servicio de IA.#Por qué los entrevistadores hacen esta pregunta
Todo equipo dice que su API está "documentada", y casi todo equipo ha visto esa documentación desincronizarse del código real en pocos sprints. Preguntar sobre contract testing comprueba si el candidato realmente ha lanzado una API de la que otros equipos dependen — porque el modo de falla que el contract testing existe para prevenir (un equipo de frontend construyendo contra una colección de Postman de tres releases atrás, o dos servicios backend en desacuerdo sobre si un campo es nullable) es algo que solo se aprende a temer después de vivir el incidente que provocó.
#La brecha entre pruebas unitarias, de integración y la realidad
Las pruebas unitarias verifican que tu código hace lo que le dijiste que hiciera. Las pruebas de integración end-to-end verifican que dos servicios reales concuerdan, pero solo ejecutando ambos juntos de verdad — normalmente en un entorno de staging compartido que todos los equipos se disputan, que tarda en levantarse, y que falla por razones ajenas a tu cambio (el servicio de alguien más está caído, una base de datos compartida tiene datos semilla obsoletos). El contract testing se ubica deliberadamente entre ambos extremos: verifica que las entradas y salidas reales de un servicio cumplen un contrato acordado, en el CI de cada equipo, de forma independiente, sin necesitar nunca tener ambos sistemas reales corriendo a la vez. Un consumidor puede validar contra el contrato con un mock; un proveedor puede validar sus respuestas reales contra ese mismo contrato, y si ambos pasan, la integración en staging es un trámite en vez de un descubrimiento.
#Dos variantes, y cuándo gana cada una
"Contract testing" cubre dos prácticas relacionadas pero distintas:
| Orientado al proveedor (spec-first) | Orientado al consumidor (p. ej. Pact) | |
|---|---|---|
| Fuente de verdad | Un documento OpenAPI/AsyncAPI, escrito y versionado como código | Expectativas grabadas por las propias pruebas de cada consumidor |
| Mejor encaje | APIs públicas, o consumidores numerosos/desconocidos | Un número pequeño de consumidores internos conocidos |
| Qué verifica | "Mis respuestas reales coinciden con la forma documentada para todos" | "Mis respuestas reales satisfacen exactamente lo que cada consumidor específico usa de verdad" |
| Herramientas típicas | OpenAPI + un middleware o librería de pruebas que valida el esquema | Un Pact broker; las pruebas del consumidor generan contratos, el CI del proveedor los verifica |
| Desvío que se le escapa | Un consumidor que depende de comportamiento no documentado que la spec nunca prometió | Un consumidor no descubierto para el que nadie escribió un contrato |
Ninguno reemplaza por completo al otro — muchos equipos de plataforma maduros mantienen una spec OpenAPI como contrato público y un puñado de contratos Pact para sus consumidores internos de mayor valor, porque cada uno detecta una clase distinta de desvío.
#OpenAPI como algo más que documentación
Una vez que existe un documento OpenAPI y se lo trata como fuente de verdad —escrito antes o junto con la implementación, no diseñado a la inversa después de ella—, deja de ser documentación y empieza a impulsar el flujo de trabajo de varias formas concretas: un servidor mock (herramientas como Prism pueden servir respuestas falsas realistas directamente desde la spec) permite que el trabajo de frontend arranque antes de que exista el endpoint real en el backend; Swagger UI o Redoc renderizan documentación interactiva siempre actualizada desde el mismo archivo, así que solo hay un artefacto que mantener preciso; se pueden generar SDKs de cliente y stubs de servidor en varios lenguajes a partir de ella; y —la parte que de verdad le gana el nombre de "contrato"— un middleware validador o una suite de pruebas dedicada puede afirmar que cada solicitud y respuesta real que maneja tu servicio cumple el esquema, haciendo fallar el build en el momento en que implementación y spec dejan de coincidir.
openapi: 3.0.3info:title: Orders APIversion: 1.2.0servers:- url: https://api.shop.example/v1paths:/orders/{orderId}:get:summary: Obtener un pedido por idoperationId: getOrderparameters:- name: orderIdin: pathrequired: trueschema:type: stringpattern: '^o_[a-z0-9]{6,}$'responses:'200':description: Pedido encontradocontent:application/json:schema:$ref: '#/components/schemas/Order'example:id: o_9f8e7dstatus: pendingtotal: 42.50items:- sku: ABC123qty: 2'404':description: No existe un pedido con ese idcontent:application/json:schema:$ref: '#/components/schemas/Error'/orders:post:summary: Crear un nuevo pedidooperationId: createOrderrequestBody:required: truecontent:application/json:schema:type: objectrequired: [items]properties:items:type: arrayminItems: 1items:type: objectrequired: [sku, qty]properties:sku:type: stringqty:type: integerminimum: 1responses:'201':description: Pedido creadocontent:application/json:schema:$ref: '#/components/schemas/Order'components:schemas:Order:type: objectrequired: [id, status, total, items]properties:id:type: stringstatus:type: stringenum: [pending, paid, shipped, cancelled]total:type: numberformat: floatitems:type: arrayitems:type: objectrequired: [sku, qty]properties:sku:type: stringqty:type: integerminimum: 1Error:type: objectrequired: [message]properties:message:type: string
#Aplicarlo en el CI, no solo describirlo
Una spec que nadie contrasta con la realidad es una promesa sin consecuencia por romperla. La forma en que los equipos convierten OpenAPI en un contract test real es ejecutando el servicio de verdad y validando sus respuestas reales contra el esquema en cada build:
// contract.test.js — hace fallar el build en cuanto la implementación se desvía de la spec publicadaimport request from "supertest";import jestOpenAPI from "jest-openapi";import app from "../src/app";jestOpenAPI("./openapi.yaml");test("GET /orders/:id coincide con el contrato publicado", async () => {const res = await request(app).get("/orders/o_9f8e7d");expect(res.status).toBe(200);expect(res).toSatisfyApiSpec();});test("POST /orders rechaza un sku faltante según el contrato", async () => {const res = await request(app).post("/orders").send({ items: [{ qty: 2 }] });expect(res.status).toBe(400);});