Skip to content
SP StackPractices
intermediate Por StackPractices

API Mocking para Testing

Construye tests confiables mockeando APIs externas con WireMock, MockServer y MSW para eliminar flakiness y testear casos edge.

Temas: testing

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:

EstrategiaNivelIdeal Para
HTTP server proxyRedTests de integración; verificar clientes HTTP reales
Request interceptorAplicaciónTests unitarios; mocking unificado browser/Node
Service virtualizationSistemaAPIs stateful complejas; contract testing

Jerarquía de request matching:

  1. URL exactaGET /users/123
  2. Patrón de pathGET /users/*
  3. Match de headerContent-Type: application/json
  4. Match de body — JSON path o regex en request body
  5. State-dependent — Retornar respuesta diferente en segunda llamada

Variantes

HerramientaLenguajeMejor Feature
WireMockJava/CualquieraEscenarios stateful; proxy recording
MSWTypeScriptMismos mocks en browser, Node y tests
MockServerCualquieraAPI de expectativas JSON; verificación
responsesPythonBasado en decoradores; assertions simples
NockNode.jsAPI 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

  1. Mockear métodos internos: Testeas el mock, no el código
  2. Matchers demasiado permisivos: Matchers any() dejan pasar bugs que matchers específicos detectan
  3. Sin cobertura de escenarios de error: Solo testear 200 OK omite la mitad del código de manejo de errores
  4. Estado mutable compartido: Estado de mock global filtra entre tests
  5. 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.