Copy-pasteable, verified patterns for testing external APIs with HTTPX. All examples conform to VEXFOX v0.1.0 public interfaces.
Record and replay a standard API endpoint. Vexfox normalizes query parameter ordering so '?page=1&limit=20' matches '?limit=20&page=1'.
import httpx
import vexfox
def test_fetch_users():
# Recording mode: ONCE (default).
# Generates 'fixtures/users.json' on first run; replays offline thereafter.
with vexfox.use_cassette("fixtures/users.json"):
response = httpx.get("https://api.example.com/v1/users?page=1&limit=20")
assert response.status_code == 200
data = response.json()
assert len(data["items"]) == 20Demonstrates structural JSON matching. Differences in JSON dictionary key order or whitespace formatting do not cause false test failures.
import httpx
import vexfox
def test_create_order():
payload = {
"item_id": 104,
"quantity": 2,
"currency": "USD"
}
with vexfox.use_cassette("fixtures/create_order.json"):
response = httpx.post(
"https://api.example.com/orders",
json=payload,
headers={"Content-Type": "application/json"}
)
assert response.status_code == 201
assert response.json()["status"] == "pending"Authorization headers, live secret tokens, and sensitive body keys are scrubbed in memory before the cassette touches disk.
import httpx
from vexfox import VexfoxConfig, use_cassette
custom_config = VexfoxConfig(
sensitive_headers={"x-service-token"},
sensitive_body_keys={"client_secret", "refresh_token"},
redaction_marker="[REDACTED]",
)
def test_oauth_token_exchange():
with use_cassette("fixtures/oauth_exchange.json", config=custom_config):
response = httpx.post(
"https://auth.example.com/oauth/token",
headers={
"Authorization": "Bearer sensitive_access_token_999",
"X-Service-Token": "internal_infra_secret"
},
json={
"grant_type": "authorization_code",
"client_secret": "ultra_secret_client_key_441"
}
)
assert response.status_code == 200
# In fixtures/oauth_exchange.json, all secrets are replaced with [REDACTED]!Full support for async workflows, asyncio test suites, and concurrent task isolation using ContextVars.
import httpx
import vexfox
import pytest
@pytest.mark.asyncio
async def test_async_batch_fetch():
async with vexfox.use_cassette("fixtures/async_batch.json"):
async with httpx.AsyncClient() as client:
response = await client.get("https://api.example.com/v1/metrics")
assert response.status_code == 200
metrics = response.json()
assert "cpu_usage" in metricsGuarantees that test suites in CI cannot accidentally call out to live external APIs. Fails fast if a cassette is missing or unexpected request occurs.
import httpx
from vexfox import RecordMode, use_cassette
def test_ci_offline_pipeline():
# Enforces RecordMode.REPLAY: Zero network sockets are permitted!
with use_cassette("fixtures/catalog.json", mode=RecordMode.REPLAY):
response = httpx.get("https://api.example.com/catalog")
assert response.status_code == 200
# If any unrecorded request is attempted, NetworkForbiddenInReplayError
# or RequestMismatchError is raised immediately.Use the built-in 'vexfox' fixture in your pytest tests for clean context lifecycle management.
import httpx
def test_user_billing_profile(vexfox):
"""Clean pytest fixture usage."""
with vexfox("fixtures/user_billing.json"):
response = httpx.get("https://api.example.com/users/alex/billing")
assert response.status_code == 200
assert response.json()["plan"] == "pro"