API Testing Strategy
- Tool: Python Requests (Simple & Powerful)
- Design: Chained workflows via Sessions
- Security: Testing Auth, IDOR, and Injection
- Pyramid: 80% API / 20% UI
Why API testing should form the backbone of your automation stack
UI automation is valuable β but it is slow, flaky, and expensive. Modern testing strategy follows the Testing Pyramid, which emphasizes:
- 70β80% API tests
- 10β20% UI tests
- ~10% unit tests integrated with services
API tests provide:
- β Fast feedback
- β Deterministic behavior
- β Zero UI dependency
- β Early defect detection
- β Easy shift-left integration with CI/CD
And for Python automation teams, the `requests` library is the optimal choice β simple, battle-tested, powerful, and flexible.
1. Why Python requests Is Ideal for API Testing
Unlike Selenium/Appium, API testing needs: Native handling of sessions/cookies, Precise control of headers, Payload generation (JSON/XML), SSL verification handling, Multipart uploads, Streaming responses, Retry & timeout controls, Authentication strategies (Basic, Bearer, OAuth2).
`requests` handles all of this elegantly.
Example: Creating a session
import requests
session = requests.Session()
session.headers.update({"Content-Type": "application/json"})A session persists: Cookies, Headers, Authentication, Connection pooling. This is essential for chained workflows and enterprise-grade test suites.
2. Chaining API Requests & Workflow Automation
Real-world API testing means chaining dependent calls: Login β Create Entity β Validate Entity β Update Entity β Delete Entity. You must extract values from one response to feed the next.
End-to-End Workflow Example
import requests
session = requests.Session()
# 1. Login
auth_payload = {"username": "admin", "password": "secret"}
login_resp = session.post("https://api.example.com/auth/login", json=auth_payload)
token = login_resp.json()["token"]
# Add token to all subsequent calls
session.headers.update({"Authorization": f"Bearer {token}"})
# 2. Create item
item_payload = {"name": "Mobile Charger", "price": 199}
create_resp = session.post("https://api.example.com/items", json=item_payload)
item_id = create_resp.json()["id"]
# 3. Get item
get_resp = session.get(f"https://api.example.com/items/{item_id}")
assert get_resp.status_code == 200
# 4. Delete item
delete_resp = session.delete(f"https://api.example.com/items/{item_id}")
assert delete_resp.status_code == 204Best Practices
- β Always use Session() for workflows
- β Extract dynamic data from responses instead of hardcoding
- β Validate status codes and body schema
- β Log request/response for debugging
- β Handle failures gracefully with clear errors
Pitfalls
- β Forgetting to persist cookies β authentication breaks
- β Hard-coded IDs β brittle tests
- β Ignoring timeouts β hanging test runs
- β Running workflows with stale tokens β inconsistent failures
3. API Test Architecture (Python)
A scalable API testing framework needs:
a. HTTP Client Wrapper
Wrap requests so you can inject: Base URL, Timeout defaults, Retry logic, Logging, Error handlers.
class APIClient:
def __init__(self, base_url):
self.session = requests.Session()
self.base_url = base_url
def post(self, endpoint, **kwargs):
return self.session.post(self.base_url + endpoint, timeout=10, **kwargs)
def get(self, endpoint, **kwargs):
return self.session.get(self.base_url + endpoint, timeout=10, **kwargs)
def delete(self, endpoint, **kwargs):
return self.session.delete(self.base_url + endpoint, timeout=10, **kwargs)b. Test Data Builder: Generate dynamic payloads.
c. Schema Validators: Using jsonschema or pydantic.
d. Common Assertions: Status code, Response schema, Time taken, Headers, Business rules.
4. Security Testing with Python Requests
Your API tests must validate not just correctness, but security posture.
a. Authentication & Authorization
Validate: Token expiry, Token scope, Role-based access, IDOR vulnerabilities, Missing authentication, Brute-force rate limits.
Example: Detecting IDOR
# User A's token
session.headers.update({"Authorization": "Bearer UA_TOKEN"})
resp = session.get("https://api.example.com/users/USER_B")
assert resp.status_code == 403 # Should not allow accessb. SQL Injection Testing
payload = {"username": "admin' OR '1'='1", "password": "test"}
resp = session.post("/auth/login", json=payload)
assert resp.status_code in (400, 401)c. Security Header Validation
resp = session.get("/items")
assert resp.headers.get("X-Content-Type-Options") == "nosniff"
assert "max-age" in resp.headers.get("Strict-Transport-Security", "")5. Performance Testing at the API Level
Functional correctness is not enough. API responses must also meet performance SLAs.
a. Measure Response Time
resp = session.get("/items")
assert resp.elapsed.total_seconds() < 0.2 # < 200ms SLAb. Load-lite Testing with Requests (For quick checks, not full load testing)
import time
for _ in range(50):
start = time.time()
r = session.get("/health")
assert r.status_code == 200
assert (time.time() - start) < 0.1For real load testing, use: k6, JMeter, Locust, Gatling, or OctoPerf.
6. Error Handling, Timeouts & Retries (Enterprise Critical)
Timeouts: Always set connect timeout and read timeout. `session.get('/items', timeout=(3, 10))`
Retry Logic: Use `urllib3.Retry` with `requests.adapters`.
Error Classes: Create custom exceptions for Bad response schema, Invalid status code, Token errors, Infra errors. This makes debugging fast and deterministic.
7. Validations Every API Test Must Perform
- β Status code
- β Body schema
- β Business rules
- β Headers
- β Response time
- β Pagination logic (if applicable)
- β Error responses (negative testing)
Negative testing is often ignored but critical: Missing fields, Invalid inputs, Unsupported HTTP methods, Unauthorized access.
8. Common Pitfalls in API Automation
- β Hardcoding tokens instead of generating them
- β Assuming payload formats instead of validating
- β Ignoring backward compatibility
- β Using UI tests to validate backend logic
- β Not cleaning up test data
- β Running API tests that modify prod environments
- β Lack of replay protection verification (idempotency tests missing)
9. Final Best Practices Summary
π For Python Requests: Use Session() always, Set timeouts and retries, Enable logging, Externalize configs, Layer your client + tests, Validate schemas consistently.
π For API Workflows: Chain requests dynamically, Extract IDs/tokens, Validate status + schema + time.
π For Architecture: Use client wrapper, Use data builders, Use schema validators, Use environment-specific configuration.
π For Quality: Add negative tests, Add security tests, Add basic performance thresholds, Integrate contract tests.

