# Testes de API: REST, GraphQL e Microserviços

**Meta Description:** Domine testes de API: REST, GraphQL, microservices. Aprenda a usar Postman, RestAssured, pytest e automação de testes de API para garantir qualidade de integrações.

---

## 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.

```python
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).

```python
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

```python
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}% succeeded"
```

### 4. Testes de Segurança

```python
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": "<script>alert('xss')</script>"}
    response = requests.post(f"{BASE_URL}/usuarios", json=payload)
    
    if response.status_code == 201:
        data = response.json()
        assert "<script>" 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

```python
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

```python
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

```python
# 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

```json
// 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

```python
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

```json
{
  "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

```bash
# 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)

```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

```python
# 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% dos endpoints críticos, pelo menos 80% dos endpoints públicos.
