Integration testing validates how different components, services, and systems work together. Comprehensive Integration Test Documentation ensures teams understand system boundaries, API contracts, data flows, and dependency relationships. This guide explores effective approaches to documenting integration tests for modern distributed systems.
Understanding Integration Test Documentation
Integration tests sit between unit tests (testing individual components) and end-to-end tests (testing complete user journeys). They verify that interfaces between systems communicate correctly, data transforms properly, and error handling works across boundaries.
Key Documentation Components
Effective integration test documentation includes:
- API Contract Specifications: Clear interface definitions and expectations
- System Interface Maps: Visual and textual representations of integration points
- Data Flow Diagrams: How data moves and transforms between systems
- Dependency Mapping: Understanding service relationships and call chains
- Test Data Requirements: Specific data needed for integration scenarios
- Environment Configuration: Setup requirements for integration testing
- Error Scenarios: How systems handle failures at integration points
API Contract Documentation
Contract-First Testing Approach
Document API contracts before implementation for clear expectations:
# user-service-contract.yaml
openapi: 3.0.0
info:
title: User Service API
version: 2.1.0
description: User management and authentication service
paths:
/api/users/{userId}:
get:
summary: Retrieve user details
operationId: getUserById
parameters:
- name: userId
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: User found
content:
application/json:
schema:
$ref: '#/components/schemas/User'
example:
id: "550e8400-e29b-41d4-a716-446655440000"
email: "user@example.com"
firstName: "John"
lastName: "Doe"
status: "active"
createdAt: "2024-01-15T10:30:00Z"
'404':
description: User not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500':
description: Internal server error
/api/users:
post:
summary: Create new user
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserRequest'
responses:
'201':
description: User created
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'400':
description: Invalid request
'409':
description: User already exists
components:
schemas:
User:
type: object
required:
- id
- email
- status
properties:
id:
type: string
format: uuid
email:
type: string
format: email
firstName:
type: string
lastName:
type: string
status:
type: string
enum: [active, inactive, suspended]
createdAt:
type: string
format: date-time
Integration Test Scenarios from Contracts
# test_user_service_integration.py
import pytest
import requests
from jsonschema import validate
from datetime import datetime
class TestUserServiceIntegration:
"""
Integration tests for User Service API
Based on contract: user-service-contract.yaml v2.1.0
"""
BASE_URL = "https://api-staging.example.com"
@pytest.fixture
def auth_headers(self):
"""Get authentication headers for tests"""
return {
"Authorization": "Bearer test_token",
"Content-Type": "application/json"
}
def test_get_user_success_contract(self, auth_headers):
"""
Verify GET /api/users/{userId} returns data matching contract
Contract: 200 response must match User schema
"""
user_id = "550e8400-e29b-41d4-a716-446655440000"
response = requests.get(
f"{self.BASE_URL}/api/users/{user_id}",
headers=auth_headers
)
# Verify status code per contract
assert response.status_code == 200
# Verify response structure matches contract
data = response.json()
assert "id" in data
assert "email" in data
assert "status" in data
# Verify data types
assert isinstance(data["id"], str)
assert isinstance(data["email"], str)
assert data["status"] in ["active", "inactive", "suspended"]
# Verify UUID format
from uuid import UUID
UUID(data["id"]) # Raises ValueError if invalid
def test_get_user_not_found_contract(self, auth_headers):
"""
Verify GET /api/users/{userId} returns 404 for non-existent user
Contract: 404 response with Error schema
"""
non_existent_id = "00000000-0000-0000-0000-000000000000"
response = requests.get(
f"{self.BASE_URL}/api/users/{non_existent_id}",
headers=auth_headers
)
assert response.status_code == 404
assert "error" in response.json() or "message" in response.json()
def test_create_user_contract_validation(self, auth_headers):
"""
Verify POST /api/users validates request per contract
Contract: 400 for invalid request, 201 for success
"""
# Test invalid email format
invalid_payload = {
"email": "not-an-email",
"firstName": "John",
"lastName": "Doe"
}
response = requests.post(
f"{self.BASE_URL}/api/users",
json=invalid_payload,
headers=auth_headers
)
assert response.status_code == 400
# Test valid creation
valid_payload = {
"email": f"test_{datetime.now().timestamp()}@example.com",
"firstName": "John",
"lastName": "Doe"
}
response = requests.post(
f"{self.BASE_URL}/api/users",
json=valid_payload,
headers=auth_headers
)
assert response.status_code == 201
# Verify created user matches contract
created_user = response.json()
assert created_user["email"] == valid_payload["email"]
assert created_user["status"] == "active" # Default per contract
System Interface Mapping
Integration Points Documentation
# System Integration Map
## Architecture Overview
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ │ Web Frontend │─────▶│ API Gateway │─────▶│ User Service │ └─────────────────┘ └──────────────────┘ └─────────────────┘ │ │ │ ▼ │ ┌─────────────────┐ │ │ Auth Service │ │ └─────────────────┘ ▼ │ ┌──────────────────┐ │ │ Order Service │◀─────────────┘ └──────────────────┘ │ ▼ ┌──────────────────┐ │ Payment Gateway │ │ (External) │ └──────────────────┘
## Integration Points
### 1. Frontend ↔ API Gateway
**Protocol**: HTTPS/REST
**Authentication**: JWT Bearer Token
**Data Format**: JSON
**Integration Tests**: `tests/integration/frontend_api/`
| Endpoint | Method | Purpose | Test Coverage |
|----------|--------|---------|---------------|
| /api/login | POST | User authentication | ✅ Happy path, invalid creds, expired token |
| /api/products | GET | Product listing | ✅ Pagination, filtering, empty results |
| /api/orders | POST | Order creation | ✅ Valid order, insufficient stock, payment failure |
### 2. API Gateway ↔ User Service
**Protocol**: gRPC
**Authentication**: Service-to-Service mTLS
**Data Format**: Protobuf
**Integration Tests**: `tests/integration/gateway_user/`
| RPC Method | Purpose | Error Scenarios Tested |
|------------|---------|------------------------|
| GetUser | Fetch user details | User not found, service timeout |
| ValidateToken | Token verification | Invalid token, expired token, revoked token |
| UpdateProfile | User data update | Validation errors, concurrent updates |
### 3. Order Service ↔ Payment Gateway
**Protocol**: HTTPS/REST
**Authentication**: API Key + HMAC Signature
**Data Format**: JSON
**Integration Tests**: `tests/integration/order_payment/`
| Integration Flow | Test Scenarios |
|------------------|----------------|
| Payment Authorization | Successful auth, insufficient funds, card declined |
| Payment Capture | Immediate capture, delayed capture, partial capture |
| Refund Processing | Full refund, partial refund, refund failure |
| Webhook Handling | Payment success, payment failure, duplicate webhook |
Dependency Graph
# service-dependencies.yaml
services:
api-gateway:
depends_on:
- user-service
- order-service
- product-service
integration_tests:
- test_user_authentication_flow
- test_order_placement_flow
- test_product_search_flow
order-service:
depends_on:
- user-service (authentication)
- inventory-service (stock validation)
- payment-gateway (external)
- notification-service (order confirmation)
integration_tests:
- test_order_creation_with_auth
- test_inventory_validation
- test_payment_processing
- test_order_notifications
failure_scenarios:
- inventory_service_down: "Fallback to cached stock levels"
- payment_gateway_timeout: "Queue for retry, notify user"
- notification_service_down: "Queue notifications, order proceeds"
payment-service:
depends_on:
- stripe-api (external)
- paypal-api (external)
integration_tests:
- test_stripe_integration
- test_paypal_integration
- test_payment_provider_failover
circuit_breaker:
enabled: true
failure_threshold: 5
timeout: 30s
half_open_requests: 3
Data Flow Documentation
Request/Response Flow Mapping
## Order Creation Data Flow
### 1. Request Flow
Client Request ↓ [API Gateway] - Validates JWT, rate limiting ↓ (gRPC) [Auth Service] - Verifies user permissions ↓ (returns user_id, permissions) [API Gateway] - Enriches request with user context ↓ (REST) [Order Service] - Receives order request ↓ (gRPC) [Inventory Service] - Checks stock availability ↓ (returns stock_status) [Order Service] - Creates order record (status: pending) ↓ (REST) [Payment Gateway] - Processes payment ↓ (webhook) [Order Service] - Updates order (status: confirmed) ↓ (Message Queue) [Notification Service] - Sends confirmation email ↓ Response to Client
### 2. Data Transformation
| Stage | Input Schema | Output Schema | Transformation |
|-------|--------------|---------------|----------------|
| Client → Gateway | `{ items: [{productId, qty}], shipping: {...} }` | + `userId`, `timestamp`, `requestId` | Enrichment |
| Gateway → Order | Enriched request | + `userEmail`, `userName` from Auth | User lookup |
| Order → Inventory | `{ productId: string, quantity: number }[]` | `{ available: boolean, reservationId?: string }` | Stock check |
| Order → Payment | `{ amount, currency, orderId }` | `{ transactionId, status, authCode }` | Payment processing |
| Order → Notification | `{ orderId, userId, items, total }` | Email template with order details | Template rendering |
Integration Test for Data Flow
# test_order_flow_integration.py
import pytest
from unittest.mock import Mock, patch
import json
class TestOrderCreationDataFlow:
"""
End-to-end integration test for order creation data flow
Tests data transformation across service boundaries
"""
def test_complete_order_flow_data_transformation(self):
"""
Verify data transforms correctly through entire order flow
"""
# 1. Client request
client_request = {
"items": [
{"productId": "PROD-001", "quantity": 2},
{"productId": "PROD-002", "quantity": 1}
],
"shipping": {
"address": "123 Main St",
"city": "Springfield",
"zipCode": "12345"
}
}
# 2. API Gateway enrichment
with patch('auth_service.verify_token') as mock_auth:
mock_auth.return_value = {
"userId": "USER-123",
"email": "user@example.com",
"name": "John Doe"
}
enriched_request = self.api_gateway.enrich_request(
client_request,
headers={"Authorization": "Bearer test_token"}
)
# Verify enrichment
assert enriched_request["userId"] == "USER-123"
assert "timestamp" in enriched_request
assert "requestId" in enriched_request
# 3. Inventory check
with patch('inventory_service.check_stock') as mock_inventory:
mock_inventory.return_value = {
"PROD-001": {"available": True, "reservationId": "RES-001"},
"PROD-002": {"available": True, "reservationId": "RES-002"}
}
inventory_response = self.order_service.validate_inventory(
enriched_request["items"]
)
# Verify inventory data structure
assert all(item["available"] for item in inventory_response.values())
# 4. Order creation
order_data = self.order_service.create_order(enriched_request)
# Verify order data structure
assert order_data["orderId"]
assert order_data["status"] == "pending"
assert order_data["userId"] == "USER-123"
assert len(order_data["items"]) == 2
# 5. Payment processing
with patch('payment_gateway.process_payment') as mock_payment:
mock_payment.return_value = {
"transactionId": "TXN-999",
"status": "authorized",
"authCode": "AUTH123"
}
payment_request = {
"amount": order_data["total"],
"currency": "USD",
"orderId": order_data["orderId"]
}
payment_response = self.payment_service.process(payment_request)
# Verify payment data
assert payment_response["status"] == "authorized"
assert payment_response["transactionId"]
# 6. Order confirmation
confirmed_order = self.order_service.confirm_order(
order_data["orderId"],
payment_response["transactionId"]
)
# Verify final order state
assert confirmed_order["status"] == "confirmed"
assert confirmed_order["paymentDetails"]["transactionId"] == "TXN-999"
# 7. Notification data
notification_payload = self.notification_service.prepare_confirmation(
confirmed_order
)
# Verify notification includes all necessary data
assert notification_payload["to"] == "user@example.com"
assert notification_payload["template"] == "order_confirmation"
assert notification_payload["data"]["orderId"] == order_data["orderId"]
assert notification_payload["data"]["customerName"] == "John Doe"
Error Handling and Resilience Testing
Circuit Breaker Integration Tests
# test_circuit_breaker_integration.py
import pytest
from datetime import datetime, timedelta
class TestCircuitBreakerIntegration:
"""
Test circuit breaker behavior across service boundaries
"""
def test_payment_service_circuit_breaker(self):
"""
Verify circuit breaker opens after threshold failures
and prevents cascading failures
"""
# Configure circuit breaker
circuit_breaker_config = {
"failure_threshold": 5,
"timeout": 30, # seconds
"half_open_max_requests": 3
}
# Simulate 5 consecutive failures
for i in range(5):
response = self.order_service.process_payment({
"orderId": f"ORDER-{i}",
"amount": 100.00,
"force_failure": True # Test flag
})
assert response["status"] == "failed"
# 6th request should be rejected by circuit breaker (OPEN state)
response = self.order_service.process_payment({
"orderId": "ORDER-6",
"amount": 100.00
})
assert response["status"] == "circuit_open"
assert "Payment service temporarily unavailable" in response["message"]
# Verify circuit breaker state
cb_state = self.order_service.get_circuit_breaker_state("payment_service")
assert cb_state["state"] == "OPEN"
assert cb_state["failure_count"] >= 5
def test_circuit_breaker_half_open_recovery(self):
"""
Verify circuit breaker transitions to HALF_OPEN and recovers
"""
# Set circuit to OPEN state
self._open_circuit_breaker("inventory_service")
# Wait for timeout period
time.sleep(31) # timeout is 30 seconds
# First request after timeout (HALF_OPEN state)
response = self.order_service.check_inventory({
"productId": "PROD-001",
"quantity": 1
})
# Should allow request through
assert response["status"] == "success"
# Verify state is HALF_OPEN
cb_state = self.order_service.get_circuit_breaker_state("inventory_service")
assert cb_state["state"] == "HALF_OPEN"
# Send 2 more successful requests (half_open_max_requests = 3)
for i in range(2):
response = self.order_service.check_inventory({
"productId": f"PROD-{i}",
"quantity": 1
})
assert response["status"] == "success"
# Circuit should now be CLOSED (recovered)
cb_state = self.order_service.get_circuit_breaker_state("inventory_service")
assert cb_state["state"] == "CLOSED"
assert cb_state["failure_count"] == 0
Best Practices for Integration Test Documentation
Documentation Structure
# Integration Test Documentation Template
## 1. Integration Overview
- **Services Involved**: List all systems in integration
- **Integration Type**: Synchronous/Asynchronous, REST/gRPC/Message Queue
- **Business Context**: Why this integration exists
- **Criticality**: P0/P1/P2 classification
## 2. Contract Specification
- API contract (OpenAPI/Protobuf/AsyncAPI)
- Request/Response examples
- Error responses
- Authentication requirements
## 3. Test Scenarios
- Happy path scenarios
- Error scenarios
- Edge cases
- Performance requirements
## 4. Data Requirements
- Test data setup
- Data dependencies
- Cleanup procedures
## 5. Environment Setup
- Service dependencies
- Configuration requirements
- Mock services (if any)
## 6. Execution Instructions
- Pre-requisites
- Execution commands
- Expected results
- Troubleshooting guide
Integration Test Checklist
Aspect | Verification | Status |
---|---|---|
Contract Validation | ✅ Request/response match schema | |
Authentication | ✅ Valid auth accepted, invalid rejected | |
Authorization | ✅ Permissions enforced correctly | |
Data Validation | ✅ Invalid data rejected with clear errors | |
Error Handling | ✅ Errors propagate correctly | |
Idempotency | ✅ Duplicate requests handled properly | |
Timeout Handling | ✅ Timeouts trigger fallback behavior | |
Circuit Breaker | ✅ Opens on failures, recovers properly | |
Data Consistency | ✅ Distributed transactions complete or rollback | |
Performance | ✅ Response times within SLA |
Conclusion
Comprehensive Integration Test Documentation ensures teams understand how services interact, what contracts govern those interactions, and how data flows through the system. By documenting API contracts, mapping dependencies, visualizing data flows, and testing error scenarios, you create a robust foundation for reliable distributed systems.
Remember: integration tests are your safety net for service communication. Invest in clear documentation, maintain contracts rigorously, and test both happy paths and failure scenarios to build resilient, well-integrated systems.