AI-Automated API Testing
API testing solves multiple tasks simultaneously: functionality (correct responses), contract testing (schema validation), performance (latency under load), security (auth, injection, rate limits). An AI system covers all four layers, generating tests from OpenAPI specs and analyzing real traffic.
Generating Tests from OpenAPI Specification
import yaml
import json
from langchain_openai import ChatOpenAI
from pathlib import Path
class APITestGenerator:
CONTRACT_TEST_PROMPT = """Create pytest tests for API endpoint.
Endpoint: {method} {path}
OpenAPI Spec:
{spec}
Tests should cover:
1. **Happy path**: valid request → expected response
2. **Schema validation**: response matches OpenAPI schema (use jsonschema)
3. **Auth**: request without token → 401, invalid token → 401/403
4. **Validation errors**: missing required fields → 422, wrong types → 422
5. **Boundary values**: min/max string length, numeric limits
6. **Business rules**: specific rules from endpoint description
Use: pytest + httpx + jsonschema
Base URL via pytest fixture: base_url
Auth token via fixture: auth_token
Return test code."""
def __init__(self):
self.llm = ChatOpenAI(model="gpt-4o", temperature=0.1)
def generate_from_openapi(self, spec_path: str) -> dict[str, str]:
"""Generates tests for all endpoints from OpenAPI spec"""
with open(spec_path) as f:
spec = yaml.safe_load(f)
test_files = {}
for path, methods in spec.get("paths", {}).items():
for method, endpoint_spec in methods.items():
test_code = self._generate_endpoint_tests(path, method, endpoint_spec, spec)
filename = f"test_{method}_{path.replace('/', '_').strip('_')}.py"
test_files[filename] = test_code
return test_files
def _generate_endpoint_tests(
self,
path: str,
method: str,
endpoint_spec: dict,
full_spec: dict
) -> str:
# Resolve $ref
resolved_spec = self._resolve_refs(endpoint_spec, full_spec)
return self.llm.invoke(
self.CONTRACT_TEST_PROMPT.format(
method=method.upper(),
path=path,
spec=json.dumps(resolved_spec, indent=2)
)
).content
Traffic-Based Test Generation
class TrafficBasedTestGenerator:
"""Generates tests from HAR files or proxy logs"""
def generate_from_har(self, har_path: str) -> list[str]:
"""Generates regression tests from recorded traffic"""
with open(har_path) as f:
har = json.load(f)
entries = har["log"]["entries"]
api_calls = [
e for e in entries
if "api" in e["request"]["url"] or
e["response"]["content"].get("mimeType", "").startswith("application/json")
]
tests = []
for entry in api_calls[:50]: # top 50 unique requests
test = self._generate_regression_test(entry)
tests.append(test)
return tests
def _generate_regression_test(self, entry: dict) -> str:
request = entry["request"]
response = entry["response"]
prompt = f"""Create pytest regression test from recorded HTTP interaction.
Request:
- Method: {request['method']}
- URL: {request['url']}
- Headers: {json.dumps({h['name']: h['value'] for h in request.get('headers', [])[:5]})}
- Body: {request.get('postData', {}).get('text', '')[:500]}
Response:
- Status: {response['status']}
- Body: {response['content'].get('text', '')[:500]}
Create test that:
1. Reproduces request (with parametrized test data instead of real)
2. Checks status code
3. Validates response schema (keys, types)
4. Doesn't hardcode real data (replace with fixtures)
Return pytest code."""
return self.llm.invoke(prompt).content
API Security Testing
class APISecurityTester:
SECURITY_PROMPTS = {
"sql_injection": [
"' OR '1'='1", "'; DROP TABLE users;--",
"1 UNION SELECT NULL,NULL,NULL--",
"' AND SLEEP(5)--"
],
"nosql_injection": [
'{"$gt": ""}', '{"$where": "this.password.length > 0"}',
'{"$regex": ".*"}'
],
"xss": [
"<script>alert('xss')</script>",
"javascript:alert(1)",
'"><img src=x onerror=alert(1)>'
]
}
async def test_injection_resilience(
self,
endpoint: str,
param_name: str,
client
) -> list[dict]:
results = []
for attack_type, payloads in self.SECURITY_PROMPTS.items():
for payload in payloads:
response = await client.post(
endpoint,
json={param_name: payload}
)
# App should return 400/422, not 500 or data
results.append({
"attack_type": attack_type,
"payload": payload,
"status": response.status_code,
"vulnerable": response.status_code == 500 or
self._contains_db_error(response.text)
})
return results
Load Testing via Locust
LOCUST_PROMPT = """Create Locust load test for API.
Endpoints for load:
{endpoints}
Create:
- HttpUser class with tasks for each endpoint
- Realistic distribution: frequent operations → higher weight
- @task(3) for reads, @task(1) for writes
- between(1, 5) for wait_time
- Error handling via on_failure
Target: 100 RPS, P95 latency < 500 ms.
Return Python code for locustfile.py."""
CI/CD Configuration
# API test pyramid in CI
api-tests:
contract:
run: pytest tests/api/contract/ -v
on: [push, pull_request]
security:
run: pytest tests/api/security/ -v
on: [pull_request]
performance:
run: locust -f tests/api/locustfile.py --headless -u 50 -r 5 --run-time 2m
on: [manual, schedule] # don't block PR
Case study: fintech startup REST API, 45 endpoints. Team spent 3 days on API regression testing before each release. Generated 180 contract tests from OpenAPI spec and 60 security tests. Tests discovered: 2 endpoints without auth checks, 1 SQL injection in report filter, incorrect Unicode handling in names (500 instead of 422).
Timeframe: generator from OpenAPI + contract tests: 2–3 weeks; security and performance tests: 3–4 additional weeks.







