Testes de Integração de API: Estratégias, Ferramentas e Boas Práticas
Introdução
Testes unitários verificam funções individuais. Testes de API verificam endpoints individuais. Mas nenhum dos dois diz se seus serviços realmente funcionam juntos. Esse é o trabalho dos testes de integração.
Os testes de integração de API validam que múltiplos serviços, bancos de dados e sistemas externos se comunicam corretamente por meio de suas interfaces de API. Em uma arquitetura de microsserviços onde dezenas de serviços dependem uns dos outros, os testes de integração são a rede de segurança que captura contratos incompatíveis, falhas de rede e problemas de serialização de dados.
Este guia cobre as estratégias, ferramentas, exemplos de código e boas práticas que você precisa para construir testes de integração de API confiáveis, desde cenários simples de dois serviços até fluxos de trabalho complexos com múltiplos serviços.
O que São Testes de Integração de API?
Os testes de integração de API verificam se sistemas conectados funcionam corretamente juntos por meio de suas interfaces de API. Diferentemente dos testes unitários (que testam código em isolamento) ou dos testes end-to-end (que testam o sistema completo pela UI), os testes de integração se concentram nas fronteiras entre serviços.
O que os Testes de Integração Validam
- Os dados fluem corretamente entre serviços (o Serviço A envia dados que o Serviço B pode analisar)
- Os contratos são respeitados (a API retorna os campos e tipos que o consumidor espera)
- O tratamento de erros funciona através das fronteiras de serviço (o Serviço A lida graciosamente com os erros do Serviço B)
- A autenticação se propaga corretamente pela cadeia de serviços
- As interações com o banco de dados funcionam corretamente (consultas, transações, migrações)
- APIs externas se comportam como esperado (gateways de pagamento, serviços de email, dados de terceiros)
Testes de Integração vs. Outros Tipos de Teste
| Tipo de Teste | Escopo | Velocidade | Dependências | Detecta |
|---|---|---|---|---|
| Teste Unitário | Função/classe única | Rápido (ms) | Mockadas | Bugs de lógica |
| Teste de API | Endpoint único | Rápido (ms-s) | Frequentemente mockadas | Violações de contrato de API |
| Teste de Integração | Múltiplos serviços | Médio (s) | Reais ou containers | Incompatibilidades de interface |
| Teste E2E | Sistema completo + UI | Lento (min) | Todos reais | Bugs de fluxo do usuário |
Estratégias de Teste de Integração de API
Estratégia 1: Teste de Integração Big Bang
Conecte todos os serviços de uma vez e teste o sistema completo. Simples de entender, mas difícil de depurar quando os testes falham, você não sabe qual serviço causou a falha.
Melhor para: Sistemas pequenos com poucos serviços.
Estratégia 2: Teste de Integração Incremental
Adicione e teste serviços um de cada vez. Comece com o serviço central e adicione serviços conectados incrementalmente.
Top-down: Comece com o gateway de API e simule (mock) serviços downstream, depois substitua os mocks por serviços reais um a um.
Bottom-up: Comece com os serviços de nível mais baixo (banco de dados, cache) e construa para cima.
Sanduíche: Combine top-down e bottom-up, encontrando no meio.
Melhor para: Sistemas médios a grandes onde você precisa isolar falhas.
Estratégia 3: Contract Testing
Defina contratos entre consumidor e provedor, depois verifique cada lado independentemente. Essa é a abordagem mais escalável para microsserviços.
Melhor para: Arquiteturas de microsserviços com muitas dependências entre serviços.
Testes de Integração de API na Prática com Código
JavaScript: Testando Integração de Serviços com Supertest
// tests/integration/orders.test.js const request = require('supertest'); const app = require('../../src/app'); const db = require('../../src/db');describe('Orders API Integration', () => { let userId; let productId;
beforeAll(async () => { // Seed database with test data await db.migrate.latest(); const user = await db('users').insert({ name: 'Test User', email: 'test@example.com' }).returning('id'); userId = user[0].id;
const product = await db('products').insert({ name: 'Widget', price: 29.99, stock: 100 }).returning('id'); productId = product[0].id;});
afterAll(async () => { await db('orders').del(); await db('products').del(); await db('users').del(); await db.destroy(); });
test('Creating an order updates product stock', async () => { // Create order via API const orderRes = await request(app) .post('/api/orders') .send({ userId, items: [{ productId, quantity: 3 }] }) .expect(201);
expect(orderRes.body.total).toBe(89.97); // 29.99 * 3 // Verify stock was decremented const productRes = await request(app) .get(`/api/products/${productId}`) .expect(200); expect(productRes.body.stock).toBe(97); // 100 - 3});
test('Order fails when insufficient stock', async () => { const res = await request(app) .post('/api/orders') .send({ userId, items: [{ productId, quantity: 9999 }] }) .expect(400);
expect(res.body.error).toContain('Insufficient stock');});
test('Order creation sends notification to user service', async () => { const orderRes = await request(app) .post('/api/orders') .send({ userId, items: [{ productId, quantity: 1 }] }) .expect(201);
// Verify notification was created const notifRes = await request(app) .get(`/api/users/${userId}/notifications`) .expect(200); const orderNotif = notifRes.body.find( n => n.type === 'order_confirmation' ); expect(orderNotif).toBeDefined(); expect(orderNotif.orderId).toBe(orderRes.body.id);
}); });
Python: Testando com pytest e Docker
# tests/integration/test_order_flow.py import pytest import requests import timeAPI_URL = "http://localhost:3000/api"
@pytest.fixture(scope="module") def test_user(): """Create a test user and return their data.""" response = requests.post(f"{API_URL}/users", json={ "name": "Integration Test User", "email": "integration@test.com" }) assert response.status_code == 201 yield response.json() # Cleanup requests.delete(f"{API_URL}/users/{response.json()['id']}")
@pytest.fixture(scope="module") def test_product(): """Create a test product.""" response = requests.post(f"{API_URL}/products", json={ "name": "Test Widget", "price": 19.99, "stock": 50 }) assert response.status_code == 201 yield response.json() requests.delete(f"{API_URL}/products/{response.json()['id']}")
class TestOrderIntegration: def test_complete_order_flow(self, test_user, test_product): """Test the full order lifecycle across services.""" # Step 1: Create order order_response = requests.post(f"{API_URL}/orders", json={ "userId": test_user["id"], "items": [{"productId": test_product["id"], "quantity": 2}] }) assert order_response.status_code == 201 order = order_response.json() assert order["total"] == 39.98 # 19.99 * 2
# Step 2: Verify payment was processed payment_response = requests.get( f"{API_URL}/orders/{order['id']}/payment" ) assert payment_response.status_code == 200 assert payment_response.json()["status"] == "completed" # Step 3: Verify inventory updated product_response = requests.get( f"{API_URL}/products/{test_product['id']}" ) assert product_response.status_code == 200 assert product_response.json()["stock"] == 48 # 50 - 2 def test_order_rollback_on_payment_failure(self, test_user, test_product): """Verify stock is restored when payment fails.""" initial_stock = requests.get( f"{API_URL}/products/{test_product['id']}" ).json()["stock"] # Create order with invalid payment method to trigger failure order_response = requests.post(f"{API_URL}/orders", json={ "userId": test_user["id"], "items": [{"productId": test_product["id"], "quantity": 1}], "paymentMethod": "invalid_card" }) assert order_response.status_code == 400 # Verify stock was not decremented current_stock = requests.get( f"{API_URL}/products/{test_product['id']}" ).json()["stock"] assert current_stock == initial_stock
Contract Testing com Pact
O contract testing é a abordagem mais eficaz para testar integrações de API em microsserviços. O consumidor define o que espera do provedor, e ambos os lados verificam independentemente.
Teste do Lado do Consumidor (JavaScript)
// consumer/tests/userServiceClient.pact.test.js const { PactV3 } = require('@pact-foundation/pact'); const { UserServiceClient } = require('../src/userServiceClient');const provider = new PactV3({ consumer: 'OrderService', provider: 'UserService', });
describe('UserService Client', () => { test('fetches user by ID', async () => { provider .given('a user with ID 1 exists') .uponReceiving('a request for user 1') .withRequest({ method: 'GET', path: '/api/users/1', headers: { Accept: 'application/json' }, }) .willRespondWith({ status: 200, headers: { 'Content-Type': 'application/json' }, body: { id: 1, name: 'John Doe', email: 'john@example.com', }, });
await provider.executeTest(async (mockServer) => { const client = new UserServiceClient(mockServer.url); const user = await client.getUser(1); expect(user.id).toBe(1); expect(user.name).toBe('John Doe'); });
}); });
Verificação do Lado do Provedor
// provider/tests/pactVerification.test.js const { Verifier } = require('@pact-foundation/pact');describe('UserService Provider Verification', () => { test('validates contract with OrderService', async () => { const verifier = new Verifier({ providerBaseUrl: 'http://localhost:3001', pactUrls: ['./pacts/OrderService-UserService.json'], stateHandlers: { 'a user with ID 1 exists': async () => { // Set up the required state in the provider await db('users').insert({ id: 1, name: 'John Doe', email: 'john@example.com', }); }, }, });
await verifier.verifyProvider();
}); });
Usando Docker para Testes de Integração
Os testes de integração precisam de dependências reais (bancos de dados, caches, filas de mensagens). O Docker Compose torna isso gerenciável:
# docker-compose.test.yml version: '3.8' services: api: build: . environment: DATABASE_URL: postgres://test:test@db:5432/testdb REDIS_URL: redis://cache:6379 depends_on: db: condition: service_healthy cache: condition: service_starteddb: image: postgres:16 environment: POSTGRES_DB: testdb POSTGRES_USER: test POSTGRES_PASSWORD: test healthcheck: test: pg_isready -U test interval: 5s retries: 5
cache: image: redis:7-alpine
test-runner: build: context: . dockerfile: Dockerfile.test environment: API_URL: http://api:3000 depends_on: - api command: npm run test:integration
# Run integration tests
docker-compose -f docker-compose.test.yml up --build --abort-on-container-exit
Testes de Integração em CI/CD
# GitHub Actions integration tests
name: Integration Tests
on:
push:
branches: [main, develop]
jobs:
integration:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_DB: testdb
POSTGRES_USER: test
POSTGRES_PASSWORD: test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:7-alpine
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm run migrate
env:
DATABASE_URL: postgres://test:test@localhost:5432/testdb
- run: npm run test:integration
env:
DATABASE_URL: postgres://test:test@localhost:5432/testdb
REDIS_URL: redis://localhost:6379
Boas Práticas para Testes de Integração de API
1. Use Dependências Reais Sempre que Possível
Testes de integração com dependências mockadas não são testes de integração, são testes unitários disfarçados. Use bancos de dados reais, caches reais e filas de mensagens reais via containers Docker.
2. Isole os Dados de Teste
Cada teste deve criar seus próprios dados e limpar depois. Use transações de banco de dados que fazem rollback após cada teste, ou truncate as tabelas entre execuções de teste.
3. Teste Cenários de Erro
Não teste apenas o caminho feliz. Teste o que acontece quando as dependências estão indisponíveis, retornam erros ou retornam dados inesperados.
4. Mantenha os Testes Rápidos
Os testes de integração são mais lentos que os testes unitários, mas não devem ser lentos. Mire abaixo de 30 segundos para toda a sua suite de integração. Use execução paralela e configuração compartilhada quando for seguro.
5. Use Contract Testing para APIs entre Equipes
Quando equipes diferentes possuem serviços diferentes, o contract testing (com Pact ou similar) é mais prático do que executar todos os serviços juntos.
6. Combine com Outros Tipos de Teste
Os testes de integração complementam os testes de API REST, testes de carga e testes de segurança. Use o Qodex.ai para gerar automaticamente testes funcionais e de segurança, depois adicione testes de integração para fluxos de trabalho entre serviços.
Para uma visão geral completa das ferramentas de teste, veja nossa comparação de ferramentas de teste de API.
Perguntas Frequentes
Qual é a diferença entre testes de API e testes de integração de API?
Os testes de API validam um único endpoint de API em isolamento, códigos de status corretos, corpos de resposta e tratamento de erros. Os testes de integração de API validam que múltiplos serviços funcionam juntos por meio de suas APIs, os dados fluem corretamente, os contratos são respeitados e os erros se propagam adequadamente através das fronteiras de serviço.
Testes de API e testes de integração são a mesma coisa?
Não exatamente. Os testes de API podem ser feitos em isolamento (mockando dependências), tornando-os mais próximos dos testes unitários. Os testes de integração testam especificamente as interações entre serviços reais, bancos de dados e sistemas externos. No entanto, há uma sobreposição significativa, e muitas equipes usam testes de API como testes de integração quando testam contra dependências reais.
Como testar a integração de API sem acesso ao serviço real?
Use contract testing com ferramentas como Pact. O consumidor define as interações esperadas, e ambos os lados verificam independentemente. Você também pode usar servidores mock, WireMock ou ferramentas de virtualização de serviços para simular o serviço externo.
Quais ferramentas são melhores para testes de integração de API?
Para JavaScript: Supertest + Jest com Docker. Para Python: pytest + requests com Docker. Para contract testing: Pact. Para geração automatizada de testes: Qodex.ai. Para testes entre serviços: Docker Compose para orquestrar todos os serviços.
Como lidar com dados de teste em testes de integração?
Use transações de banco de dados que fazem rollback após cada teste, ou truncate as tabelas entre execuções de teste. Crie dados de teste em hooks setUp/beforeEach e limpe em tearDown/afterEach. Nunca compartilhe dados de teste entre testes, cada teste deve ser independente.
Os testes de integração devem ser executados em CI/CD?
Sim. Os testes de integração devem ser executados em cada push e pull request. Use Docker Compose ou containers de serviço CI/CD (como os serviços do GitHub Actions) para inicializar dependências reais. Mantenha a suite rápida focando nos caminhos críticos de integração e executando testes em paralelo.
Discover, Test, & Secure your APIs 10x Faster than before
Auto-discover every endpoint, generate functional & security tests (OWASP Top 10), auto-heal as code changes, and run in CI/CD - no code needed.
Related Blogs





