Automation
AI
Test Automation
API Testing: Key Strategies

API Testing: Key Strategies

November 29, 2025 4 min read
🎯

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

Code
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

Code
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 == 204

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

Code
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

Code
# 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 access

b. SQL Injection Testing

Code
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

Code
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

Code
resp = session.get("/items")
assert resp.elapsed.total_seconds() < 0.2   # < 200ms SLA

b. Load-lite Testing with Requests (For quick checks, not full load testing)

Code
import time

for _ in range(50):
start = time.time()
r = session.get("/health")
assert r.status_code == 200
assert (time.time() - start) < 0.1

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

Dhiraj Das

About the Author

Dhiraj Das | Senior Automation Consultant | 10+ years building test automation that actually works. He transforms flaky, slow regression suites into reliable CI pipelinesβ€”designing self-healing frameworks that don't just run tests, but understand them.

Creator of many open-source tools solving what traditional automation can't: waitless (flaky tests), sb-stealth-wrapper (bot detection), selenium-teleport (state persistence), selenium-chatbot-test (AI chatbot testing), lumos-shadowdom (Shadow DOM), and visual-guard (visual regression).

Share this article:

Get In Touch

Interested in collaborating or have a question about my projects? Feel free to reach out. I'm always open to discussing new ideas and opportunities.