API Mocking para Testing
Construye tests confiables simulando APIs externas con WireMock, MockServer y MSW para eliminar inestabilidad y probar casos límite.
Visión General
El API mocking sustituye dependencias externas reales por simulaciones controladas durante el testing. Tu suite deja de depender de la red: cero inestabilidad, ejecuciones más rápidas y casos límite como errores 500 o timeouts que se vuelven triviales de reproducir. Cada herramienta simula la frontera de forma distinta — WireMock corre como un servidor HTTP real, MSW intercepta requests in-process y responses parchea la capa HTTP de Python — así que eliges la que mejor encaje en tu stack.
Cuándo Usar
Usa este recurso cuando:
- Las APIs externas son poco confiables, lentas o tienen rate limits que bloquean pipelines de CI
- Necesitas probar el manejo de errores para HTTP 429, 503 o escenarios de timeout
- El servicio real no tiene un sandbox o ambiente de pruebas
- Quieres tests determinísticos que no fallen por cambios de terceros
Evita el API mocking cuando:
- Pruebas tu propia base de datos o lógica interna — usa un motor en memoria o Testcontainers y ejecuta lo real
- Necesitas pruebas de que el proveedor cumple su API — para eso está el contract testing
Solución
WireMock Standalone (Java)
import com.github.tomakehurst.wiremock.WireMockServer;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
public class PaymentApiMock {
private static WireMockServer wireMockServer;
public static void start() {
wireMockServer = new WireMockServer(8089);
wireMockServer.start();
wireMockServer.stubFor(
post(urlEqualTo("/payments"))
.withHeader("Content-Type", equalTo("application/json"))
.withRequestBody(matchingJsonPath("$.amount"))
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody("{\"id\": \"pay_123\", \"status\": \"succeeded\"}")
)
);
// Escenario de error
wireMockServer.stubFor(
post(urlEqualTo("/payments"))
.withRequestBody(matchingJsonPath("$.amount", equalTo("999999")))
.willReturn(aResponse()
.withStatus(400)
.withBody("{\"error\": \"amount_exceeds_limit\"}")
)
);
}
public static void stop() {
wireMockServer.stop();
}
}
MSW (Mock Service Worker) para Browser/Node
// MSW 2.x — la API `rest` de la v1 fue eliminada
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
const handlers = [
http.get('https://api.example.com/users/:id', ({ params }) => {
const { id } = params;
return HttpResponse.json({ id, name: 'Test User', email: 'test@example.com' });
}),
http.post('https://api.example.com/orders', async ({ request }) => {
const { total } = await request.json();
return HttpResponse.json({ orderId: 'ord_456', total }, { status: 201 });
}),
// Simulación de error
http.get('https://api.example.com/flaky', () => {
return HttpResponse.json({ error: 'Service Unavailable' }, { status: 503 });
})
];
const server = setupServer(...handlers);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
Python responses Library
import responses
import requests
@responses.activate
def test_payment_api():
responses.add(
responses.POST,
'https://payments.example.com/charge',
json={'id': 'ch_123', 'status': 'succeeded'},
status=200
)
result = requests.post(
'https://payments.example.com/charge',
json={'amount': 100, 'currency': 'USD'}
)
assert result.json()['status'] == 'succeeded'
assert len(responses.calls) == 1
assert responses.calls[0].request.json()['amount'] == 100
Simulación de Latencia y Fallos
Los mocks rápidos que siempre devuelven 200 esconden los bugs que importan. Hazlos lentos e inestables a propósito:
// MSW 2.x: latencia y fallos a nivel de red
import { http, HttpResponse, delay } from 'msw';
http.get('https://api.example.com/slow-report', async () => {
await delay(5000); // dispara cualquier timeout de cliente menor a 5s
return HttpResponse.json({ ok: true });
}),
http.get('https://api.example.com/unreachable', () => {
return HttpResponse.error(); // fallo de red real, no un status HTTP
})
HttpResponse.error() dispara un fallo a nivel de conexión — un reset o un error de DNS, algo que un 503 jamás reproduce. Añade llamadas a delay() para comprobar que el cliente respeta su propio presupuesto de timeout; una request que nunca resuelve duele más que un 500.
Mocks con Estado
Las APIs reales cambian de estado entre llamadas — un job pasa de pending → running → done. WireMock lo modela con escenarios, donde un stub puede transicionar el escenario tras coincidir:
// El primer poll devuelve 202, el siguiente 200 — prueba el polling sin sleeps reales
wireMockServer.stubFor(get(urlEqualTo("/jobs/42"))
.inScenario("job-lifecycle")
.whenScenarioStateIs(Scenario.STARTED)
.willReturn(aResponse().withStatus(202).withBody("{\"state\": \"running\"}"))
.willSetStateTo("done"));
wireMockServer.stubFor(get(urlEqualTo("/jobs/42"))
.inScenario("job-lifecycle")
.whenScenarioStateIs("done")
.willReturn(aResponse().withStatus(200).withBody("{\"state\": \"done\"}")));
Los escenarios permiten que un endpoint responda distinto en secuencia — la única forma sensata de probar polling, reintentos con recuperación y workflows de varios pasos.
Explicación
Tres estrategias de mocking:
| Estrategia | Nivel | Ideal Para |
|---|---|---|
| HTTP server proxy | Red | Tests de integración; verificar clientes HTTP reales |
| Request interceptor | Aplicación | Tests unitarios; mocking unificado browser/Node |
| Service virtualization | Sistema | APIs stateful complejas; contract testing |
Jerarquía de request matching:
- URL exacta —
GET /users/123 - Patrón de path —
GET /users/* - Coincidencia de header —
Content-Type: application/json - Coincidencia de body — JSON path o regex sobre el cuerpo de la request
- Dependiente de estado — Devolver una respuesta diferente en la segunda llamada
Mock vs. stub vs. fake vs. contract test — se confunden constantemente:
| Técnica | ¿Ejecuta código real? | ¿Registra llamadas? | ¿Verifica al proveedor? |
|---|---|---|---|
| Stub | No | No | No |
| Mock | No | Sí | No |
| Fake | Sí, simplificado | No | No |
| Contract test | Código real del proveedor | — | Sí |
Elige según la pregunta que respondes: «¿mi código maneja la respuesta X?» → stub o mock. «¿Mi código llama bien a la API?» → mock. «¿Mis queries funcionan?» → fake o una base de datos real. «¿El proveedor sigue cumpliendo su contrato?» → contract test.
Variantes
| Herramienta | Lenguaje | Mejor Feature |
|---|---|---|
| WireMock | Java/Cualquiera | Escenarios stateful; proxy recording |
| MSW | TypeScript | Mismos mocks en browser, Node y tests |
| MockServer | Cualquiera | API de expectativas JSON; verificación |
| responses | Python | Basado en decoradores; assertions simples |
| Nock | Node.js | API encadenada; modo recorder |
Lo que funciona
- Simula en la frontera: Mockea HTTP, no métodos internos — los tests deberían ejercitar el stack completo.
- Verifica requests, no solo responses: Asegúrate de que tu código envía el payload y headers correctos
- Usa record/replay para APIs complejas: Captura tráfico real una vez, luego replay en tests
- Mantén mocks cerca de la realidad: Actualiza mocks cuando la API real cambia; mocks obsoletos ocultan bugs
- Limpia estado entre tests: Un reset evita que el setup de un test afecte a otro
Errores Comunes
- Mockear métodos internos: Pruebas el mock, no el código
- Matchers demasiado permisivos: Matchers
any()dejan pasar bugs que matchers específicos detectan - Sin cobertura de escenarios de error: Probar solo 200 OK omite la mitad del código de manejo de errores
- Estado mutable compartido: El estado global de los mocks se filtra entre tests
- Olvidar verificar: Un test que pasa con un mock sin usar significa que nada quedó realmente probado
Lectura Adicional
- Documentación de WireMock — stubs, escenarios y record-and-play
- Documentación de MSW — interceptación de requests en browser y Node
- Documentación de MockServer — API de expectativas y verificación de requests
- responses en GitHub — la librería de Python usada arriba
Preguntas frecuentes
¿Debería simular la base de datos de mi propio servicio?
No. Usa mocks en las fronteras que no controlas — una API de terceros, una pasarela de pago, un proveedor de email. Tu base de datos es otra historia: levántala de verdad con Testcontainers o un motor en memoria. Una DB simulada te dice que el mock funciona; no dice nada de tus queries.
¿Cuál es la diferencia entre mocking y stubbing?
Los stubs devuelven respuestas prefijadas y olvidan que la llamada ocurrió. Los mocks registran cada llamada para que puedas verificarla — «¿mi código llamó a este endpoint con estos args?». Usa stubs para datos, y mocks cuando la interacción misma es lo que estás probando.
¿Los mocks pueden reemplazar al contract testing?
No. Un mock responde «si la API devuelve X, ¿mi código hace Y?» — nunca pregunta si el proveedor realmente devuelve X. Esa segunda pregunta es para lo que existen los contract tests. Quieres ambos: los mocks mantienen la suite rápida, los contratos la mantienen honesta.
¿Cómo mantengo los mocks sincronizados con la API real?
Derívalos de la spec OpenAPI del proveedor o de tráfico grabado — record-and-play de WireMock, cassettes tipo VCR — y regraba periódicamente. Un mock viejo sigue pasando tests contra una API que ya no existe; ese drift es el verdadero precio de simular.
¿Los mock servers deberían vivir en mi suite de tests o en un entorno compartido?
En la suite, levantados en cada test — los mock servers compartidos se convierten en estado mutable compartido y CI inestable. WireMock, MSW y responses corren in-process o por test; reserva un entorno compartido solo para QA exploratorio.
Recursos Relacionados
Guía de Pipelines CI/CD: Del Commit a Producción
Guía práctica para construir pipelines CI/CD con GitHub Actions, gates de testing, estrategias de despliegue y procedimientos de rollback.
GuideGuía de Estrategia de Testing de Software
Una guía práctica para construir una estrategia de testing en capas con tests unitarios, de integración, de contrato y end-to-end que tu equipo realmente mantendrá.
GuideDesarrollo Guiado por Pruebas (TDD)
Aprende TDD paso a paso: escribe un test que falle, hazlo pasar, refactoriza. Red-Green-Refactor con ejemplos reales en Python, JavaScript y Java.
RecipeStubear APIs HTTP Externos con WireMock en Java
Usá WireMock en tests de Java para stubear servicios HTTP externos. Cubre templating, simulación de delays, stubs stateful y verificación de requests.
RecipeMocks de resolvers GraphQL para desarrollo frontend
Configura resolvers GraphQL mockeados con Apollo Server para que los equipos frontend desarrollen contra una API falsa antes del backend.