Ir al contenido principal

Qué es API testing: checklist operativo para REST, GraphQL y gRPC

API Testing
Fecha de publicación: agosto 24, 2026

El API testing es la validación sistemática de las interfaces de programación de aplicaciones mediante pruebas funcionales, de contrato, rendimiento y seguridad adaptadas de forma específica a cada protocolo. En los procesos de modernización hacia arquitecturas de microservicios, aplicar un checklist genérico genera falsos negativos peligrosos: mientras que las vulnerabilidades de exposición de datos tipo BOLA y BOPLA suelen pasar desapercibidas en los entornos GraphQL, los cuellos de botella asíncronos y la saturación de conexiones quedan ocultos en los servicios gRPC de alto volumen. 

Concepto y alcance del API testing en la modernización hacia microservicios

El API testing trasciende la simple verificación de endpoints aislados. Se trata de una disciplina multidimensional que abarca la prueba funcional de lógica de negocio, el contract testing entre componentes, las pruebas de carga y la evaluación de la postura de seguridad, todo ejecutado de forma iterativa en cada despliegue. La diferencia fundamental entre validar un endpoint de forma puntual y establecer una verdadera gobernanza radica en la capacidad de mantener la trazabilidad de cada hallazgo (ya sea un defecto, una vulnerabilidad activa o una degradación de los SLA) a lo largo de todo el ciclo de vida de la aplicación.

Utilizar checklists heredados de entornos monolíticos REST genera un riesgo estructural en ecosistemas distribuidos. Mientras que en REST se suele autorizar por ruta o endpoint, GraphQL exige una autorización granular a nivel de campo (field-level authorization); por su parte, gRPC demanda un control riguroso de la concurrencia sobre canales HTTP/2 y la gestión de backpressure en streaming, mucho más allá de la mera serialización del payload.

De acuerdo con el Postman 2025 State of the API Report, aunque el 93% de las organizaciones tiene REST como estilo arquitectónico principal, el 33% opera ya con GraphQL en producción. Sin embargo, a pesar de este entorno multiprotocolo, solo el 17% de las empresas aplica contract testing en sus pipelines de CI/CD. Esta brecha metodológica explica en gran medida que reaparezcan errores en producción cuando se agregan nuevos microservicios o cambian los equipos de desarrollo.

Para resolver estas fricciones en proyectos de gran escala, las iniciativas de arquitectura de integración enterprise de Chakray proponen desacoplar las pruebas del código para convertirlas en un proceso continuo de calidad. 

El falso negativo en la migración a microservicios: un riesgo de negocio real

Para entender el impacto financiero y operativo de estos fallos, basta observar los escenarios de fricción habituales en el mercado. Un equipo de calidad que migra un núcleo monolítico hacia microservicios utilizando pruebas REST genéricas puede obtener un falso negativos (un “falso pase” en verde) que apruebe el paso a producción.

En el plano de GraphQL, la ausencia de límites en la profundidad de las consultas abre la puerta a vulnerabilidades de exposición masiva de datos (Broken Object Property Level Authorization o BOPLA), clasificadas en el OWASP API Security Top 10 (2023) bajo la categoría API3:2023. Un usuario autenticado pero sin privilegios podría ejecutar consultas anidadas a más de 15 niveles de profundidad y extraer campos sensibles de otros usuarios.

En el entorno gRPC, no probar el control de concurrencia en streaming bidireccional bajo patrones de uso reales provoca el agotamiento de recursos del canal HTTP/2, desencadenando latencias en cascada y caídas masivas bajo carga, un comportamiento que la Documentación Oficial de Rendimiento de gRPC advierte de forma explícita.

El coste de estos errores no es solo técnico: implica rollbacks de emergencia, auditorías no planificadas y pérdida de reputación. Por ello, los marcos de modernización de sistemas legacy de Chakray abogan por sustituir las pruebas aisladas por procesos de gobernanza continua que protejan el negocio de forma independiente del partner o equipo que ejecute las pruebas. 

Checklist de API testing unificado: REST vs. GraphQL vs. gRPC 

La mayoría de los enfoques tácticos del mercado fragmentan la calidad separando las pruebas por protocolo o herramienta. El enfoque gobernado integra todos los controles bajo un mismo criterio de cumplimiento para toda la organización. 

Dimensión de testing  Enfoque táctico (fragmentado) Enfoque gobernado (checklist unificado)
Cobertura multi-protocolo  Guías y checklists aislados por estilo arquitectónico.  Checklist único e integral que audita REST, GraphQL y gRPC en una sola pieza. 
Autorización (BOLA/BOPLA)  Verificación puntual del endpoint en el momento del test.  Controles por objeto y campo integrados como criterios recurrentes en cada release. 
Continuidad ante rotación  El conocimiento del test se queda en la herramienta o en el proveedor saliente.  Metodología documentada y transferible, independiente de quién ejecute las pruebas. 
Cuellos de botella en streaming  Se valida la sintaxis Protobuf pero no el comportamiento bajo >1000 streams.  Pruebas de carga que simulan tráfico real (>500 RPC/s), miden el agotamiento de recursos HTTP/2 y frenan el despliegue si la latencia p99 supera el SLA. 
Integración con la modernización  El testing se ejecuta como una fase aislada al final del proyecto.  El testing se ancla a cada hito de migración de sistemas legacy a microservicios. 

Checklist funcional, de contrato y seguridad con ejemplos de código 

1. REST APIs

  • Contrato y funcionalidad: validar el cumplimiento estricto de la especificación OpenAPI, el correcto uso de los métodos HTTP (GET, POST, PUT, DELETE) y la consistencia de los códigos de estado (200, 201, 400, 401, 403, 404, 405, 409, 415, 422, 429).
  • Seguridad (BOLA – API1:2023): verificar que la alteración de identificadores en la URL (/api/v1/orders/123 por /api/v1/orders/124) no devuelva información no autorizada ni revele la existencia del recurso mediante respuestas asimétricas.
  • API5:2023 BFLA: probar métodos administrativos (DELETE /api/v1/users/{id}) con token de usuario estándar.
  • Mass assignment: enviar {«role»: «admin», «is_verified»: true} en un PATCH y verificar que el servidor los ignora (API3, cara de escritura).
  • Paginación: ?limit=100000 → verificar tope duro del servidor.
  • Fuzzing dirigido por contrato: schemathesis run –checks all openapi.yaml genera casos negativos desde la propia OpenAPI.
import pytest

GHOST_ID = "00000000-0000-0000-0000-000000000000"   # ID válido en formato, inexistente
LEAK_KEYS = {"owner", "user_id", "customer_id", "email", "tenant_id"}


def _leaks(node) -> bool:
    """Búsqueda recursiva de metadatos del propietario en cualquier nivel."""
    if isinstance(node, dict):
        return bool(LEAK_KEYS & node.keys()) or any(_leaks(v) for v in node.values())
    if isinstance(node, list):
        return any(_leaks(v) for v in node)
    return False


def _safe_json(resp):
    try:
        return resp.json()
    except ValueError:
        return {}


def test_owner_can_access_own_resource(client, user_b_token, user_b_order_id):
    """Control positivo: sin esto, un endpoint roto haría pasar el test de BOLA."""
    r = client.get(f"/api/v1/orders/{user_b_order_id}",
                   headers={"Authorization": f"Bearer {user_b_token}"})
    assert r.status_code == 200


@pytest.mark.parametrize("method", ["get", "put", "patch", "delete"])
def test_bola_and_enumeration_prevention(client, user_a_token, user_b_order_id, method):
    """
    API1:2023 (BOLA) + prevención de enumeración.
    El criterio no es 'denegar', es 'denegar de forma INDISTINGUIBLE'.
    """
    headers = {"Authorization": f"Bearer {user_a_token}", "Accept": "application/json"}
    foreign = getattr(client, method)(f"/api/v1/orders/{user_b_order_id}", headers=headers)
    ghost   = getattr(client, method)(f"/api/v1/orders/{GHOST_ID}", headers=headers)

    # 1. Denegación efectiva
    assert foreign.status_code in (403, 404), "Acceso a recurso ajeno permitido: BOLA activo"

    # 2. Homogeneidad: recurso ajeno e inexistente deben ser indistinguibles
    assert foreign.status_code == ghost.status_code, (
        f"Oráculo de enumeración: ajeno={foreign.status_code} vs inexistente={ghost.status_code}"
    )
    assert _safe_json(foreign) == _safe_json(ghost)

    # 3. Sin fuga de metadatos del propietario en ningún nivel
    assert not _leaks(_safe_json(foreign))

 

2. GraphQL APIs

  • Contrato y esquema: validar la coherencia del esquema SDL completo, rover subgraph check / graphql-inspector diff para breaking changes, política de @deprecated, y validación de composición en federación
  • Seguridad y abuso: es crucial separar dos vectores de ataque. Por un lado, la denegación de servicio (API4:2023) se mitiga implementando límites de profundidad (Query Depth < 10) y complejidad. Por otro lado, para evitar la exposición masiva de datos (BOPLA – API3:2023), se debe asegurar la autorización a nivel de campo (field-level) en los resolvers. Si un usuario no tiene permiso, el servidor debe retornar  null en ese campo sin revelar mensajes descriptivos.
  • Introspección deshabilitada en producción (API8:2023) y field suggestions desactivadas («Did you mean ‘password’?» reconstruye el esquema aunque la introspección esté cerrada).
  • Amplificación por aliases y batching: 1.000 aliases del mismo campo login en una sola petición evaden el rate limiting por request.
  • Persisted queries / allowlist como control definitivo para APIs de cliente propio.
  • Coste real vs. profundidad: el análisis de complejidad debe ponderar argumentos de paginación (first: 10000), si no es cosmético.
  • N+1 y DataLoader: un resolver sin batching convierte una query legítima en una tormenta de queries a BD. Es un problema de rendimiento y de disponibilidad.
  • Autenticación en subscriptions (WebSocket): el handshake suele quedar fuera del middleware de auth HTTP.
DEEP_NESTED_QUERY = """
query ProfundidadExcesiva {
  user { orders { items { product { category { suppliers { details { id } } } } } } }
}
"""

def test_query_depth_limit_enforced(graphql_client, user_token):
    """API4:2023 - Unrestricted Resource Consumption."""
    r = graphql_client.post("/graphql", json={"query": DEEP_NESTED_QUERY},
                            headers={"Authorization": f"Bearer {user_token}"})

    # 200 (transporte legacy) o 400 (application/graphql-response+json) son ambos válidos
    assert r.status_code in (200, 400)
    body = r.json()
    assert body.get("data") is None, "La consulta se ejecutó: no hay límite de profundidad"

    errors = body.get("errors") or []
    assert errors, "Sin errores: el límite de profundidad no está activo"

    codes = {e.get("extensions", {}).get("code", "") for e in errors}
    msg = " ".join(e.get("message", "") for e in errors).lower()
    assert (codes & {"GRAPHQL_VALIDATION_FAILED", "QUERY_TOO_COMPLEX", "DEPTH_LIMIT_EXCEEDED"}
            or any(k in msg for k in ("depth", "complexity", "exceeded", "profundidad")))


SENSITIVE_FIELD_QUERY = """
query CamposSensibles($id: ID!) {
  employee(id: $id) { id name salary }
}
"""

def test_bopla_field_level_authorization(graphql_client, low_privilege_token, foreign_employee_id):
    """
    API3:2023 - BOPLA. Requisito de esquema: `salary` DEBE ser nullable,
    o el null propagará y anulará el objeto `employee` completo.
    """
    r = graphql_client.post("/graphql",
                            json={"query": SENSITIVE_FIELD_QUERY,
                                  "variables": {"id": foreign_employee_id}},
                            headers={"Authorization": f"Bearer {low_privilege_token}"})
    assert r.status_code == 200
    employee = r.json()["data"]["employee"]

    assert employee["name"], "La respuesta parcial debe conservar los campos autorizados"
    assert employee["salary"] is None, "BOPLA: campo sensible expuesto sin autorización"

    # El error, si existe, no debe confirmar la existencia ni el nombre del campo
    for e in r.json().get("errors", []):
        assert "salary" not in e.get("message", "").lower()

 

Flujo de Validación GraphQL: Depth Limiting y Field-Level Auth (BOPLA)

Flujo de Validación GraphQL: Depth Limiting y Field-Level Auth (BOPLA)

3. gRPC Services

  • Contrato: sincronización estricta de las definiciones Protocol Buffers (.proto) entre clientes y servidores.
  • Rendimiento y seguridad: validar la exigencia estricta de mTLS (TLS Mutuo) en la comunicación servicio a servicio, comprobando mediante pruebas automatizadas que el servidor rechaza (falla de forma segura) cualquier intento de conexión sin un certificado cliente válido, expirado o revocado. Aplicar políticas de timeout por cada llamada RPC y validar la resistencia del canal HTTP/2 midiendo el manejo de backpressure en escenarios de alta concurrencia.
  • Reflection deshabilitada en producción (equivalente a la introspección de GraphQL: expone todo el .proto).
  • Límite de tamaño de mensaje (max_receive_message_length, 4 MB por defecto) y compresión: vector de DoS por decompression bomb.
  • Compatibilidad de contrato con buf: buf lint + buf breaking –against ‘.git#branch=main’ en el PR.
  • Retry/hedging vía service config y keepalive/GOAWAY: sin ellos, un rolling update genera errores que se confundirán con fallos de carga.
  • Rotación de certificados mTLS / SPIFFE-SVID: probar el comportamiento durante la rotación.

Nota: aunque el siguiente script en Python ilustra cómo validar la lógica funcional de concurrencia y los timeouts, para inyectar estrés a nivel de infraestructura y saturar canales HTTP/2 en pipelines CI/CD reales, recomendamos integrar herramientas especializadas como ghz.

import grpc
from concurrent.futures import ThreadPoolExecutor
import user_service_pb2
import user_service_pb2_grpc

def execute_rpc_call(stub, user_id):
    """
    Ejecuta una llamada gRPC con timeout estricto de 2.0s y captura agotamientos del canal.
    """
    request = user_service_pb2.UserRequest(id=user_id)
    try:
        response = stub.GetUser(request, timeout=2.0)
        return ("SUCCESS", response)
    except grpc.RpcError as e:
        if e.code() == grpc.StatusCode.RESOURCE_EXHAUSTED:
            return ("BACKPRESSURE_TRIGGERED", e.details())
        elif e.code() == grpc.StatusCode.DEADLINE_EXCEEDED:
            return ("TIMEOUT", e.details())
        return ("ERROR", e.code())

def test_grpc_streaming_backpressure_under_load():
    """
    Simula carga masiva sobre canales HTTP/2 para validar el control de concurrencia.
    """
    channel = grpc.insecure_channel("localhost:50051")
    stub = user_service_pb2_grpc.UserServiceStub(channel)
    user_ids = [f"usr_{i}" for i in range(500)]
    
    with ThreadPoolExecutor(max_workers=200) as executor:
        futures = [executor.submit(execute_rpc_call, stub, uid) for uid in user_ids]
        results = [f.result() for f in futures]
    
    successful_calls = [r for r in results if r[0] == "SUCCESS"]
    backpressure_calls = [r for r in results if r[0] == "BACKPRESSURE_TRIGGERED"]
    
    # Garantizar que las llamadas fallidas hayan sido gestionadas ordenadamente por backpressure
    assert len(successful_calls) + len(backpressure_calls) == len(user_ids)
    channel.close()

 

Gobernanza continua del testing: metodología transferible entre partners

Para evitar que los hallazgos técnicos desaparezcan cuando rota un equipo o cambia el proveedor de testing, las organizaciones necesitan implantar una matriz operativa de trazabilidad. Siguiendo marcos de referencia en seguridad y arquitectura distribuida, como la guía NIST SP 800-204B para el control de acceso basado en atributos (ABAC) en arquitecturas de Microservicios, los resultados de las pruebas deben consolidarse en una matriz transparente de control: 

Hallazgo  Control aplicado  Protocolo  Responsable  Criterio de aceptación  Estado en release 
BOPLA en el campo salary  Autorización por campo en resolver + Profundidad < 8 + Complejidad < 800.  GraphQL  Lead QA / Arch Sec  Retorno de null para no autorizados; test automático en CI/CD; latencia p99 < 150ms.  Validado en v2.3 
Timeout bajo carga (>500 RPC/s)  Gestor de backpressure en HTTP/2 + Timeout de 2s por llamada.  gRPC  Platform Engineer  Carga concurrente con cero fallos de memoria; latencia p99 < 200ms.  Validado en v2.3 
BOLA en /api/v1/invoices/{id}  Validación de propiedad (ownership) en el middleware de autenticación.  REST  Backend Lead  Respuesta 403/404 homogénea; prueba de regresión integrada en el Pull Request.  Pendiente v2.4 

 

Esta aproximación metodológica se complementa con las prácticas de gobernanza de APIs de Chakray, las cuales integran las validaciones bajo la estrategia Shift-Left Security Testing en las pipelines de CI/CD. Además, la incorporación de técnicas de Fuzzing y DAST (Dynamic Application Security Testing) permite inyectar cargas útiles malformadas (payloads) de forma automatizada para descubrir vulnerabilidades no documentadas (Zero-days) antes del paso a producción, asegurando un proceso de traspaso (handoff) transparente y sin fricción entre partners tecnológicos. 

FAQ 

¿Cuál es la diferencia entre pruebas funcionales y pruebas de contrato en APIs?

Las pruebas funcionales evalúan si la lógica de negocio de un endpoint aislado responde correctamente a las entradas recibidas. Las pruebas de contrato (contract testing) comprueban que el esquema de datos y los tipos acordados entre el servicio proveedor y el consumidor no se rompan tras nuevos despliegues.

¿Cómo validar autorización a nivel de campo en GraphQL sin exponer información sensible?

Se debe configurar el resolver para que verifique las credenciales del usuario antes de resolver el campo sensible. Si la solicitud carece de permisos, la respuesta GraphQL debe devolver null en dicho campo y evitar mensajes de error explícitos que confirmen la existencia del dato.

¿Qué métricas son críticas en pruebas de carga sobre gRPC con streaming?

Es fundamental monitorizar la latencia por mensaje (percentiles p95 y p99), el volumen de mensajes por segundo, el comportamiento de la memoria bajo saturación y, especialmente, la tasa de errores de tipo RESOURCE_EXHAUSTED, la cual evidencia cuellos de botella en la gestión de canales HTTP/2.

Garantiza la calidad y gobernanza en tu arquitectura de APIs con Chakray

Modernizar una arquitectura hacia microservicios distribuidos mediante REST, GraphQL y gRPC exige evolucionar las estrategias de pruebas para evitar falsos negativos que comprometan la seguridad o la disponibilidad en producción.

En Chakray, acompañamos a las organizaciones en el diseño de arquitecturas resilientes a través de nuestros servicios de consultoría de integración y APIs, implantando marcos de gobernanza técnica, automatización de pruebas y aseguramiento de la calidad diseñados a la medida de ecosistemas enterprise complejos. Si necesitas acelerar la madurez de tu estrategia QA o buscas el respaldo de una dirección de implementación de integración, ponte en contacto con nuestro equipo de expertos.

¡Habla con nuestros expertos!

Contacta con nuestro equipo y descubre las tecnologías de vanguardia que potenciarán tu negocio.

contáctanos