TL;DR

  • API testing verifica que servicios backend funcionan correctamente sin UI — más rápido y confiable que tests E2E
  • Testea: status codes, cuerpo de respuesta, headers, manejo de errores, autenticación, validación de esquemas, performance
  • Herramientas: Postman (manual/aprendizaje), REST Assured (Java), Supertest (Node.js), requests (Python)
  • Automatiza en CI/CD — APIs cambian frecuentemente, atrapa breaking changes temprano
  • Cubre happy path y escenarios de error (400s, 401, 404, 500)
  • Valida esquemas de respuesta para prevenir drift de contratos entre frontend y backend

Ideal para: Backend developers, ingenieros QA, cualquiera testeando microservicios Omite si: Solo necesitas testear sitios estáticos o frontends simples

Tus tests de frontend pasan. Los usuarios reportan que la app está rota. La API cambió, y nadie testeó el contrato.

API testing atrapa estos problemas antes de que lleguen a usuarios. Es más rápido que UI testing, más confiable, y testea la lógica de negocio real de la que depende tu aplicación.

He atrapado docenas de cambios rompientes en contratos de API — un campo renombrado, un status code cambiado, un header de paginación faltante. Cada uno habría causado downtime si no lo hubiera detectado un API test en CI.

Este tutorial enseña API testing desde cero — básicos de HTTP, convenciones REST, autenticación, manejo de errores, validación de esquemas y automatización.

¿Qué es API Testing?

API (Application Programming Interface) testing verifica que tus servicios backend funcionan correctamente. En lugar de hacer clic en un UI, envías requests HTTP directamente a endpoints y verificas respuestas.

Qué cubre API testing:

  • Funcionalidad — ¿hace el endpoint lo que debería?
  • Validación de datos — ¿están las respuestas estructuradas correctamente?
  • Manejo de errores — ¿falla gracefully?
  • Autenticación — ¿está el acceso controlado apropiadamente?
  • Cumplimiento de esquema — ¿coincide la respuesta con el contrato?
  • Performance — ¿puede manejar carga?

Por qué importa API testing:

  • Más rápido que UI tests — sin renderizado de browser, milisegundos vs segundos
  • Más estable — sin selectores flaky o problemas de timing
  • Feedback más temprano — testea antes de que exista el frontend
  • Mejor cobertura — testea edge cases imposibles via UI

Dónde encajan los API tests en la pirámide de testing:

        /  UI Tests  \          ← Lentos, caros, pocos
       / Integración  \         ← API tests viven aquí
      /  Unit Tests   \         ← Rápidos, baratos, muchos

Los API tests son el punto ideal — suficientemente rápidos para correr en cada commit, pero suficientemente profundos para encontrar bugs de integración reales.

Fundamentos HTTP

Antes de testear APIs, entiende los básicos de HTTP.

Métodos HTTP

GET     /users          # Obtener todos los usuarios
GET     /users/123      # Obtener usuario 123
POST    /users          # Crear nuevo usuario
PUT     /users/123      # Reemplazar usuario 123
PATCH   /users/123      # Actualizar partes de usuario 123
DELETE  /users/123      # Eliminar usuario 123
MétodoPropósitoTiene BodyIdempotente
GETLeer datosNo
POSTCrear recursoNo
PUTReemplazar recurso
PATCHActualización parcialNo
DELETEEliminar recursoOpcional

La idempotencia importa para testing: Llamar GET o PUT 5 veces debería producir el mismo resultado que una vez. POST crea un nuevo recurso cada vez. Tus tests deben considerar esto — tests de PUT pueden reintentarse, tests de POST necesitan limpieza.

Códigos de Estado

2xx Éxito
├── 200 OK              # Request exitoso
├── 201 Created         # Recurso creado
├── 204 No Content      # Éxito, nada que retornar

4xx Errores de Cliente
├── 400 Bad Request     # Input inválido
├── 401 Unauthorized    # Auth faltante/inválido
├── 403 Forbidden       # Auth válido, sin permiso
├── 404 Not Found       # Recurso no existe
├── 409 Conflict        # Conflicta con estado actual
├── 422 Unprocessable   # Validación falló
├── 429 Too Many Req    # Rate limit excedido

5xx Errores de Servidor
├── 500 Internal Error  # Bug del servidor
├── 502 Bad Gateway     # Error upstream
├── 503 Unavailable     # Servidor sobrecargado/mantenimiento

Error común: Solo verificar 200. Siempre verifica el código específico esperado — un 200 cuando esperabas 201 significa un problema.

Testing con Postman

Postman es la forma más fácil de empezar con API testing.

Primer Request

  1. Abre Postman
  2. Ingresa URL: https://jsonplaceholder.typicode.com/posts/1
  3. Método: GET
  4. Click en Send

Agregando Tests

En Postman, agrega tests JavaScript en la pestaña “Tests”:

// Verificación de status code
pm.test("Status code is 200", function () {
    pm.response.to.have.status(200);
});

// Tiempo de respuesta
pm.test("Response time is less than 500ms", function () {
    pm.expect(pm.response.responseTime).to.be.below(500);
});

// Validación de body
pm.test("Has correct structure", function () {
    const json = pm.response.json();
    pm.expect(json).to.have.property("id");
    pm.expect(json).to.have.property("title");
    pm.expect(json.id).to.eql(1);
});

Colecciones, variables y encadenamiento

// Variables de entorno
pm.environment.set("baseUrl", "https://api.example.com");

// Encadenar: extraer token de login, usar en siguiente request
pm.test("Save auth token", function () {
    const response = pm.response.json();
    pm.environment.set("token", response.token);
    pm.environment.set("userId", response.user.id);
});

Ejecutar colecciones desde CLI con Newman:

npm install -g newman
newman run collection.json -e production.json --reporters cli,html

REST API Testing con Código

Python con requests + pytest

import requests
import pytest

BASE_URL = "https://api.example.com"

class TestUsersAPI:

    def test_get_users_returns_list(self):
        response = requests.get(f"{BASE_URL}/users")

        assert response.status_code == 200
        assert isinstance(response.json(), list)
        assert len(response.json()) > 0

    def test_create_user(self):
        payload = {
            "name": "John Doe",
            "email": "john@example.com"
        }

        response = requests.post(
            f"{BASE_URL}/users",
            json=payload,
            headers={"Content-Type": "application/json"}
        )

        assert response.status_code == 201
        data = response.json()
        assert data["name"] == payload["name"]
        assert "id" in data

    def test_get_nonexistent_user(self):
        response = requests.get(f"{BASE_URL}/users/99999")
        assert response.status_code == 404

    def test_create_user_invalid_email(self):
        payload = {"name": "John", "email": "not-an-email"}
        response = requests.post(f"{BASE_URL}/users", json=payload)
        assert response.status_code == 400

JavaScript con Supertest

const request = require('supertest');
const app = require('../src/app');

describe('Users API', () => {
  test('GET /users returns list of users', async () => {
    const response = await request(app)
      .get('/users')
      .expect(200)
      .expect('Content-Type', /json/);

    expect(Array.isArray(response.body)).toBe(true);
  });

  test('POST /users creates new user', async () => {
    const newUser = { name: 'John Doe', email: 'john@example.com' };

    const response = await request(app)
      .post('/users')
      .send(newUser)
      .expect(201);

    expect(response.body).toMatchObject(newUser);
    expect(response.body.id).toBeDefined();
  });
});

Java con REST Assured

import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.*;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

public class UsersApiTest {

    @BeforeAll
    public static void setup() {
        RestAssured.baseURI = "https://api.example.com";
    }

    @Test
    public void getUsersReturnsList() {
        given()
            .when()
                .get("/users")
            .then()
                .statusCode(200)
                .contentType(ContentType.JSON)
                .body("size()", greaterThan(0));
    }

    @Test
    public void createUserReturnsCreated() {
        String requestBody = """
            {
                "name": "John Doe",
                "email": "john@example.com"
            }
            """;

        given()
            .contentType(ContentType.JSON)
            .body(requestBody)
        .when()
            .post("/users")
        .then()
            .statusCode(201)
            .body("name", equalTo("John Doe"))
            .body("id", notNullValue());
    }
}

Validación de Esquemas de Respuesta

Testear campos individuales no es suficiente. Valida toda la estructura para detectar drift de contrato.

JSON Schema con Python

from jsonschema import validate

user_schema = {
    "type": "object",
    "required": ["id", "name", "email", "createdAt"],
    "properties": {
        "id": {"type": "integer"},
        "name": {"type": "string", "minLength": 1},
        "email": {"type": "string", "format": "email"},
        "createdAt": {"type": "string", "format": "date-time"},
        "role": {"type": "string", "enum": ["admin", "user", "viewer"]}
    },
    "additionalProperties": False
}

def test_user_response_matches_schema():
    response = requests.get(f"{BASE_URL}/users/1")
    validate(instance=response.json(), schema=user_schema)

Por qué importa la validación de esquema: Un developer agrega un campo, renombra otro o cambia un tipo. Las verificaciones puntuales lo pierden. La validación de esquema detecta cada cambio estructural — es el guardián de tu contrato API.

Testing de Autenticación

Bearer Token (JWT)

# Paso 1: Login para obtener token
login_response = requests.post(
    "https://api.example.com/auth/login",
    json={"email": "user@example.com", "password": "secret"}
)
token = login_response.json()["token"]

# Paso 2: Usar token en requests
response = requests.get(
    "https://api.example.com/protected",
    headers={"Authorization": f"Bearer {token}"}
)

Testing de Escenarios de Auth

class TestAuthentication:

    def test_protected_endpoint_requires_auth(self):
        response = requests.get(f"{BASE_URL}/protected")
        assert response.status_code == 401

    def test_invalid_token_rejected(self):
        response = requests.get(
            f"{BASE_URL}/protected",
            headers={"Authorization": "Bearer invalid_token"}
        )
        assert response.status_code == 401

    def test_valid_token_grants_access(self):
        token = get_valid_token()
        response = requests.get(
            f"{BASE_URL}/protected",
            headers={"Authorization": f"Bearer {token}"}
        )
        assert response.status_code == 200

    def test_insufficient_permissions(self):
        viewer_token = get_token_for_role("viewer")
        response = requests.delete(
            f"{BASE_URL}/users/1",
            headers={"Authorization": f"Bearer {viewer_token}"}
        )
        assert response.status_code == 403

Testing GraphQL

GraphQL usa un único endpoint con queries y mutations. El testing es fundamentalmente diferente de REST.

def test_graphql_query():
    query = """
    query GetUser($id: ID!) {
        user(id: $id) { id, name, email }
    }
    """

    response = requests.post(
        f"{BASE_URL}/graphql",
        json={"query": query, "variables": {"id": "123"}}
    )

    assert response.status_code == 200
    data = response.json()
    assert "errors" not in data
    assert data["data"]["user"]["id"] == "123"

def test_graphql_invalid_query():
    """GraphQL retorna 200 con array errors, no 400"""
    response = requests.post(
        f"{BASE_URL}/graphql",
        json={"query": "{ nonExistentField }"}
    )
    assert response.status_code == 200
    assert "errors" in response.json()

Diferencia clave con REST: GraphQL siempre retorna 200 para requests HTTP válidos. Los errores aparecen en el array errors dentro del cuerpo de respuesta.

Testing de Errores

Testea cómo tu API maneja problemas. Aquí vive la mayoría de bugs.

class TestErrorHandling:

    def test_malformed_json_returns_400(self):
        response = requests.post(
            f"{BASE_URL}/users",
            data="not valid json",
            headers={"Content-Type": "application/json"}
        )
        assert response.status_code == 400

    def test_missing_required_field_returns_400(self):
        response = requests.post(
            f"{BASE_URL}/users",
            json={"name": "John"}  # falta email
        )
        assert response.status_code == 400

    def test_duplicate_email_returns_409(self):
        requests.post(f"{BASE_URL}/users", json={
            "name": "John", "email": "john@example.com"
        })
        response = requests.post(f"{BASE_URL}/users", json={
            "name": "Jane", "email": "john@example.com"
        })
        assert response.status_code == 409

Testing de Rate Limiting

La mayoría de APIs en producción tienen límites de velocidad. Testéalos.

def test_rate_limiting():
    response = requests.get(f"{BASE_URL}/users")
    assert "X-RateLimit-Limit" in response.headers
    assert "X-RateLimit-Remaining" in response.headers

def test_rate_limit_exceeded():
    for _ in range(110):  # Asumiendo límite de 100 req/min
        requests.get(f"{BASE_URL}/users")

    response = requests.get(f"{BASE_URL}/users")
    assert response.status_code == 429
    assert "Retry-After" in response.headers

Performance Testing

Load Testing con k6

import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '30s', target: 20 },
    { duration: '1m', target: 20 },
    { duration: '10s', target: 0 },
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'],
    http_req_failed: ['rate<0.01'],
  },
};

export default function () {
  const response = http.get('https://api.example.com/users');
  check(response, {
    'status is 200': (r) => r.status === 200,
    'response time OK': (r) => r.timings.duration < 500,
  });
  sleep(1);
}

Integración CI/CD

name: API Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: '3.12' }
      - run: pip install pytest requests jsonschema
      - run: pytest tests/api/ -v --tb=short
      - run: |
          npm install -g newman
          newman run collection.json -e environment.json

IA en API Testing

Las herramientas de IA aceleran significativamente el desarrollo de tests de API en 2026.

Lo que la IA hace bien:

  • Generar casos de test desde specs OpenAPI/Swagger — pega la spec, obtén 50+ tests
  • Crear datos de test válidos e inválidos para testing de límites
  • Escribir boilerplate para autenticación y operaciones CRUD
  • Convertir entre frameworks (scripts Postman a pytest, REST Assured a Supertest)

Lo que aún necesita humanos:

  • Entender requisitos de negocio y restricciones de dominio
  • Diseñar estrategia de testing (¿qué endpoints son críticos?)
  • Debuggear tests flaky por dependencias de datos
  • Interpretar resultados de performance

Prompt útil:

Genera test cases en pytest para POST /api/orders con body { customerId, items: [{productId, quantity}], couponCode? }. Cubre: orden válida, array items vacío, customerId inválido, cantidad negativa, cupón inválido. Incluye validación de JSON Schema.

FAQ

¿Qué es el testing de API?

API testing verifica que las APIs funcionan correctamente enviando requests HTTP y validando respuestas. Testea funcionalidad, validación de datos, manejo de errores, autenticación y performance. A diferencia de UI testing, API testing prueba directamente la capa de lógica de negocio — un test de API típico tarda milisegundos vs segundos para un test de browser.

¿Qué herramientas se usan para API testing?

Herramientas populares incluyen:

  • Postman — herramienta GUI para testing manual y automatización, ideal para aprender
  • REST Assured — librería Java con sintaxis BDD fluida
  • Supertest — Node.js/JavaScript API testing, integración con Jest
  • requests + pytest — Python API testing, máxima flexibilidad
  • k6 — Performance y load testing con JavaScript
  • Newman — CLI runner para colecciones Postman en CI/CD
  • Karate — framework BDD combinando API y performance testing

¿Cuál es la diferencia entre API testing y unit testing?

Unit tests verifican funciones individuales aisladas, mockeando todas las dependencias. API tests verifican endpoints HTTP completos, incluyendo routing, middleware, autenticación, operaciones de BD y formateo de respuestas. API tests son tests de integración que verifican que los componentes funcionan juntos. Se necesitan ambos: unit tests para bugs lógicos, API tests para bugs de integración.

¿Cómo testeo APIs autenticadas?

  1. Envía request de login con credenciales
  2. Extrae token de la respuesta
  3. Incluye token en header Authorization para requests siguientes
  4. Guarda token en variable de entorno para reusar
  5. Implementa refresh de token para tokens que expiran
  6. Testea ambos escenarios — autenticado y no autenticado
  7. Testea acceso basado en roles — viewer no puede borrar, admin sí

¿Cómo difiere el testing de REST API del de GraphQL?

REST usa múltiples endpoints (GET /users, POST /users) con estructura de respuesta fija. GraphQL usa un endpoint (/graphql) donde el cliente pide campos específicos. Diferencias clave: GraphQL retorna 200 incluso con errores (verifica el array errors), necesitas testear límites de profundidad de queries (prevención de DoS), y validar que los resolvers no crean N+1 queries a la BD.

¿Cuántos tests de API necesito?

Por endpoint: 1 test happy path, 2-3 tests negativos (400, 401, 404), 1 test de validación de esquema y 1 test de auth. Un recurso CRUD típico (5 endpoints) necesita 15-20 tests. Para una API con 20 recursos — 300-400 tests. Empieza por endpoints críticos de negocio — login, pagos, datos principales — luego expande cobertura.

Recursos Oficiales

Ver También