Testes de API: REST, GraphQL e Microserviços

Testes Automatizados · 6 de julho de 2026

📖 9 min de leitura

Introdução aos Testes de API

APIs (Application Programming Interfaces) são a espinha dorsal da arquitetura moderna de software. Testar APIs de forma eficaz garante que os serviços se comuniquem corretamente, retornem dados esperados e tratem erros apropriadamente.

Por Que Testar APIs?

Benefício Impacto
Detecção precoce de bugs APIs são gateway para frontend
Integração confiável Microserviços se comunicam via API
Performance API performance afeta toda a aplicação
Documentação viva Contratos de API testados
Regression testing Mudanças não quebram integrações

Tipos de Testes de API

1. Testes de Funcionalidade

Verificam se a API funciona conforme especificado.

import requests
import pytest

BASE_URL = "https://api.exemplo.com/v1"

class TestUsuarioAPI:

def test_criar_usuario(self):
"""Deve criar usuário com sucesso"""
payload = {
"nome": "João Silva",
"email": "joao@exemplo.com",
"senha": "Senha@123"
}

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

assert response.status_code == 201
data = response.json()
assert data["nome"] == payload["nome"]
assert data["email"] == payload["email"]
assert "id" in data
assert "senha" not in data # Não retorna senha!

def test_criar_usuario_email_duplicado(self):
"""Deve falhar quando email já existe"""
payload = {
"nome": "João Silva",
"email": "joao@exemplo.com", # Já existe
"senha": "Senha@123"
}

response = requests.post(
f"{BASE_URL}/usuarios",
json=payload
)

assert response.status_code == 409 # Conflict
assert "email" in response.json()["erro"].lower()

def test_buscar_usuario_por_id(self):
"""Deve retornar usuário existente"""
response = requests.get(f"{BASE_URL}/usuarios/1")

assert response.status_code == 200
data = response.json()
assert data["id"] == 1
assert "email" in data

def test_buscar_usuario_inexistente(self):
"""Deve retornar 404 para usuário inexistente"""
response = requests.get(f"{BASE_URL}/usuarios/99999")

assert response.status_code == 404

2. Testes de Contrato

Verificam se a API está em conformidade com seu contrato (OpenAPI/Swagger).

import pytest
from jsonschema import validate

schema = {
"type": "object",
"required": ["id", "nome", "email"],
"properties": {
"id": {"type": "integer"},
"nome": {"type": "string", "minLength": 1},
"email": {"type": "string", "format": "email"}
}
}

def test_usuario_response_match_schema():
response = requests.get(f"{BASE_URL}/usuarios/1")
data = response.json()

validate(instance=data, schema=schema)

3. Testes de Performance

import time
import concurrent.futures

def test_api_response_time():
"""Tempo de resposta deve ser < 500ms"""
start = time.time()
response = requests.get(f"{BASE_URL}/usuarios/1")
elapsed = (time.time() - start) * 1000

assert response.status_code == 200
assert elapsed < 500, f"Response took {elapsed}ms"

def test_concurrent_requests():
"""API deve suportar 100 requisições simultâneas"""
def make_request():
response = requests.get(f"{BASE_URL}/health")
return response.status_code == 200

with concurrent.futures.ThreadPoolExecutor(max_workers=100) as executor:
futures = [executor.submit(make_request) for _ in range(100)]
results = [f.result() for f in concurrent.futures.as_completed(futures)]

success_rate = sum(results) / len(results)
assert success_rate > 0.95, f"Only {success_rate*100}{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} succeeded"

4. Testes de Segurança

def test_sql_injection_prevention():
    """API deve prevenir SQL injection"""
    payload = {"email": "admin' OR '1'='1"}
    response = requests.post(f"{BASE_URL}/usuarios", json=payload)

# Não deve retornar dados sensíveis
assert response.status_code in [400, 422]

def test_xss_in_input(self):
"""API deve sanitizar inputs"""
payload = {"nome": "alert('xss')"}
response = requests.post(f"{BASE_URL}/usuarios", json=payload)

if response.status_code == 201:
data = response.json()
assert "" not in data["nome"]

def test_rate_limiting(self):
"""API deve limitar requisições excessivas"""
responses = []
for _ in range(105): # Limite é 100
response = requests.get(f"{BASE_URL}/usuarios/1")
responses.append(response.status_code)

# Últimas 5 devem retornar 429 (Too Many Requests)
assert 429 in responses[-5:]


GraphQL: Testando APIs Modernas

Queries

import requests

GRAPHQL_URL = "https://api.exemplo.com/graphql"

class TestGraphQL:

def test_query_basica(self):
"""Deve buscar dados com query GraphQL"""
query = """
query {
usuario(id: 1) {
id
nome
email
}
}
"""

response = requests.post(
GRAPHQL_URL,
json={"query": query}
)

assert response.status_code == 200
data = response.json()
assert "data" in data
assert "usuario" in data["data"]
assert data["data"]["usuario"]["nome"]

def test_query_com_parametros(self):
"""Deve filtrar com argumentos"""
query = """
query($limite: Int) {
produtos(limit: $limite) {
id
nome
preco
}
}
"""

response = requests.post(
GRAPHQL_URL,
json={
"query": query,
"variables": {"limite": 5}
}
)

data = response.json()
assert len(data["data"]["produtos"]) <= 5

def test_mutation_inserir(self):
"""Deve criar dado com mutation"""
mutation = """
mutation {
criarUsuario(input: {
nome: "Maria Silva"
email: "maria@exemplo.com"
senha: "Senha@123"
}) {
id
nome
email
}
}
"""

response = requests.post(GRAPHQL_URL, json={"query": mutation})

assert response.status_code == 200
data = response.json()
assert "errors" not in data
assert data["data"]["criarUsuario"]["nome"] == "Maria Silva"

Introspection para Validação

def test_graphql_introspection():
    """Deve permitir introspection do schema"""
    query = """
    {
        __schema {
            types {
                name
                kind
            }
        }
    }
    """

response = requests.post(GRAPHQL_URL, json={"query": query})
data = response.json()

assert "data" in data
type_names = [t["name"] for t in data["data"]["__schema"]["types"]]
assert "Query" in type_names


Microserviços: Testando em Ambientes Distribuídos

Service Mesh Testing

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   API GW    │────▶│   Auth Svc  │────▶│  User Svc   │
│   :8080     │     │   :8081     │     │   :8082     │
└─────────────┘     └─────────────┘     └─────────────┘
       │                                      │
       │                                      │
       ▼                                      ▼
┌─────────────┐                        ┌─────────────┐
│   Product   │                        │  Database   │
│   Svc :8083 │                        │   :5432     │
└─────────────┘                        └─────────────┘

Testes de Contrato com Pact

# Provider (microserviço de usuários)
from pact import Consumer, Provider

pact = Consumer('API-Gateway').has_pact_with(Provider('User-Service'))
pact.start_service()

try:
# Definir expectativas
(pact
.given('user with id 1 exists')
.upon_receiving('a request for user 1')
.with_request('GET', '/users/1')
.will_respond_with(200, body={
'id': 1,
'name': 'Test User',
'email': 'test@example.com'
})
.verify())

# Verificar provider
from pact.matchers import like, Term

(pact
.given('user exists')
.upon_receiving('a request for users')
.with_request('GET', '/users')
.will_respond_with(200, body=[
like({
'id': 1,
'name': Term('w+', 'Test User'),
'email': Term(r'[w.]+@[w.]+', 'test@example.com')
})
])
.verify())
finally:
pact.stop_service()

Contrato Consumer-Driven

// contract.json (gerado pelo consumer)
{
  "consumer": {
    "name": "Order-Service"
  },
  "provider": {
    "name": "User-Service"
  },
  "interactions": [
    {
      "description": "Get user details",
      "request": {
        "method": "GET",
        "path": "/users/1"
      },
      "response": {
        "status": 200,
        "body": {
          "id": 1,
          "name": "John",
          "email": "john@example.com"
        }
      }
    }
  ]
}

TestContainers para Microserviços

import pytest
from testcontainers.postgres import PostgresContainer
from testcontainers.redis import RedisContainer

@pytest.fixture(scope="module")
def postgres():
with PostgresContainer("postgres:15") as pg:
yield pg

@pytest.fixture(scope="module")
def redis():
with RedisContainer("redis:7") as rd:
yield rd

@pytest.fixture(scope="module")
def user_service(postgres, redis):
with DockerCompose(".") as compose:
# Start user-service
compose.exec("user-service", "python", "manage.py", "migrate")
port = compose.expose("user-service", 8080)
yield f"http://localhost:{port}"


Postman: Collection Runner

Collection Structure

{
  "info": {
    "name": "E-commerce API",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "item": [
    {
      "name": "Auth",
      "item": [
        {
          "name": "Login",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "pm.test('Status 200', () => {",
                  "  pm.response.to.have.status(200);",
                  "});",
                  "pm.test('Token received', () => {",
                  "  const jsonData = pm.response.json();",
                  "  pm.collectionVariables.set('token', jsonData.token);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "url": "{{baseUrl}}/auth/login",
            "body": {
              "mode": "raw",
              "raw": "{"email":"test@test.com","password":"test123"}"
            }
          }
        }
      ]
    },
    {
      "name": "Users",
      "item": [
        {
          "name": "Get All Users",
          "request": {
            "auth": {
              "type": "bearer",
              "bearer": [{"key": "token", "value": "{{token}}"}]
            },
            "method": "GET",
            "url": "{{baseUrl}}/users"
          }
        }
      ]
    }
  ]
}

Newman CLI

# Executar collection
newman run ecommerce-api.postman_collection.json 
  --environment dev.postman_environment.json 
  --reporters html,json 
  --reporter-json-export results.json 
  --reporter-html-export report.html

# Com CI/CD
newman run collection.json
--environment ${{ secrets.POSTMAN_ENV }}
--iteration-count 1
--bail


Frameworks de Teste de API

RestAssured (Java)

import static io.restassured.RestAssured.*;
import static io.restassured.matcher.RestAssuredMatchers.*;
import static org.hamcrest.Matchers.*;

class UserApiTest {

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

@Test
void criarUsuario() {
given()
.contentType("application/json")
.body("""
{
"nome": "João Silva",
"email": "joao@exemplo.com",
"senha": "Senha@123"
}
""")
.when()
.post("/usuarios")
.then()
.statusCode(201)
.body("nome", equalTo("João Silva"))
.body("email", equalTo("joao@exemplo.com"))
.body("id", notNullValue())
.body("senha", nullValue());
}

@Test
void validarSchema() {
given()
.get("/usuarios/1")
.then()
.statusCode(200)
.body(matchesJsonSchemaInClasspath("schemas/usuario.json"));
}
}

Pytest + requests

# pytest.ini
[pytest]
addopts = -v --tb=short --strict-markers
markers =
    smoke: Smoke tests
    integration: Integration tests
    slow: Slow running tests

# conftest.py
import pytest
import requests

@pytest.fixture
def api_client():
class APIClient:
base_url = "https://api.exemplo.com/v1"

def __init__(self):
self.session = requests.Session()

def create_user(self, data):
return self.session.post(f"{self.base_url}/usuarios", json=data)

def get_user(self, user_id):
return self.session.get(f"{self.base_url}/usuarios/{user_id}")

def delete_user(self, user_id):
return self.session.delete(f"{self.base_url}/usuarios/{user_id}")

return APIClient()

@pytest.fixture
def clean_user(api_client):
"""Cria usuário para teste e limpa depois"""
user_data = {
"nome": "Test User",
"email": f"test_{uuid4()}@exemplo.com",
"senha": "Senha@123"
}
response = api_client.create_user(user_data)
user_id = response.json()["id"]

yield user_id

# Cleanup
api_client.delete_user(user_id)

# test_api.py
@pytest.mark.integration
def test_criar_e_buscar_usuario(api_client):
# Create
user_data = {"nome": "Test", "email": "test@ex.com", "senha": "123"}
response = api_client.create_user(user_data)
assert response.status_code == 201
user_id = response.json()["id"]

# Read
response = api_client.get_user(user_id)
assert response.status_code == 200
assert response.json()["nome"] == "Test"


Conclusão

Testar APIs é fundamental para garantir qualidade em arquiteturas modernas. As chaves são:

  1. Cobertura completa – Funcional, contrato, performance, segurança
  2. Automação – CI/CD com collections automatizadas
  3. Contratos – Consumer-driven contracts para microservices
  4. Isolamento – TestContainers para dependências
  5. Monitoramento – API health checks em produção


FAQ

P: Postman substitui testes de código?
R: Não. Postman é excelente para exploratory e collections, mas testes em código são mais versionáveis e reutilizáveis.

P: Como testar APIs GraphQL?
R: Use GraphQL client libraries (gql, strawberry) e valide queries, mutations e subscriptions.

P: Contract testing é necessário?
R: Essencial em microservices. Consumer-driven contracts (Pact) garantem que mudanças não quebram consumidores.

P: Quanto coverage é suficiente?
R: 100{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} dos endpoints críticos, pelo menos 80{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} dos endpoints públicos.