API Mocking para Testing
Construye tests confiables mockeando APIs externas con WireMock, MockServer y MSW para eliminar flakiness y testear casos edge.
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Visión General
El API mocking reemplaza dependencias externas reales con simulaciones controladas durante el testing. Esto elimina la flakiness de red, reduce el tiempo de ejecución de tests y habilita el testing de casos edge — como errores 500 o timeouts — que son difíciles de reproducir con servicios en vivo. Herramientas modernas como WireMock, MSW y MockServer proveen request matching, response templating y capacidades de verificación que hacen que los mocks se comporten como lo real.
Cuándo Usar
Usa este recurso cuando:
- Las APIs externas son poco confiables, lentas o tienen rate limits que bloquean pipelines de CI
- Necesitas testear manejo de errores para HTTP 429, 503 o escenarios de timeout
- El servicio real no tiene un sandbox o ambiente de test
- Quieres tests determinísticos que no fallen por cambios de terceros
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
import { rest } from 'msw';
import { setupServer } from 'msw/node';
const handlers = [
rest.get('https://api.example.com/users/:id', (req, res, ctx) => {
const { id } = req.params;
return res(
ctx.status(200),
ctx.json({ id, name: 'Test User', email: 'test@example.com' })
);
}),
rest.post('https://api.example.com/orders', (req, res, ctx) => {
return res(
ctx.status(201),
ctx.json({ orderId: 'ord_456', total: req.body.total })
);
}),
// Simulación de error de red
rest.get('https://api.example.com/flaky', (req, res, ctx) => {
return res(ctx.status(503), ctx.json({ error: 'Service Unavailable' }));
})
];
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
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/* - Match de header —
Content-Type: application/json - Match de body — JSON path o regex en request body
- State-dependent — Retornar respuesta diferente en segunda llamada
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 |
Mejores Prácticas
- Mock en el boundary: Mockea HTTP, no métodos internos — los tests deberían ejercitar el stack completo. Para cobertura de integración completa, consulta end-to-end testing.
- 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
- Reset entre tests: Limpia estado para prevenir que el setup de un test afecte otro
Errores Comunes
- Mockear métodos internos: Testeas 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: Solo testear 200 OK omite la mitad del código de manejo de errores
- Estado mutable compartido: Estado de mock global filtra entre tests
- Olvidar verificar: Un test que pasa con un mock no usado significa que nada fue realmente testeado
Preguntas Frecuentes
P: ¿Debería mockear la base de datos de mi propio servicio? R: No. Usa una base de datos en memoria o TestContainers. Mockea APIs externas, no tus propias dependencias.
P: ¿Cuál es la diferencia entre mocking y stubbing? R: Los stubs retornan respuestas predefinidas. Los mocks también verifican interacciones (¿se llamó este método con estos args?).
P: ¿Los mocks pueden reemplazar el contract testing? R: No. Los mocks testean tus suposiciones sobre la API. El contract testing verifica que ambos lados concuerden en el schema.
Recursos Relacionados
CI/CD Pipeline Guide
A practical guide to building CI/CD pipelines with GitHub Actions, testing, deployment strategies, and rollback procedures.
GuideSoftware Testing Strategy Guide
A practical guide to building a layered testing strategy with unit, integration, and end-to-end tests.
RecipeEnd-to-End Testing
Write reliable end-to-end tests that simulate real user journeys across the entire application stack.
GuideTest-Driven Development (TDD) — A Practical Workflow
Learn TDD step by step: write a failing test, make it pass, refactor. Red-Green-Refactor with real examples in Python, JavaScript, and Java.
RecipeLoad Testing APIs with k6 and Threshold-Based Assertions
How to write and run load tests with k6 to measure API performance, validate SLOs, and identify bottlenecks before production deployment