12 / 65 · 14 API Testing Fundamentals · Newman and pytest for API Automation← prev⊞ allnext →☰ Read as one page
2.3pytest + requests: Full Control
For production API test suites, pytest with the requests library provides the flexibility, maintainability, and power that Newman lacks.
Project Structure
tests/
api/
conftest.py # Shared fixtures
test_auth.py # Authentication tests
test_users.py # User CRUD tests
test_orders.py # Order tests
test_error_handling.py
data/
test_users.csv # Test data files
pytest.ini # Configuration
Shared Fixtures (conftest.py)
import os
import pytest
import requests
@pytest.fixture(scope="session")
def base_url():
return os.environ.get("API_BASE_URL", "http://localhost:3000/api/v1")
@pytest.fixture(scope="session")
def auth_headers(base_url):
"""Authenticate once per test session."""
r = requests.post(f"{base_url}/auth/login", json={
"email": "test@example.com",
"password": "testpass123"
})
assert r.status_code == 200, f"Auth failed: {r.text}"
token = r.json()["access_token"]
return {"Authorization": f"Bearer {token}"}
@pytest.fixture
def api(base_url, auth_headers):
"""Pre-configured API session."""
session = requests.Session()
session.headers.update(auth_headers)
session.headers.update({"Content-Type": "application/json"})
# Store base_url on session for convenience
session._base_url = base_url
original_request = session.request
def patched_request(method, url, **kwargs):
if url.startswith("/"):
url = f"{base_url}{url}"
return original_request(method, url, **kwargs)
session.request = patched_request
return session
@pytest.fixture
def create_user(api):
"""Factory fixture for creating test users with cleanup."""
created_ids = []
def _create(name="Test User", email=None, role="viewer"):
import uuid
email = email or f"test-{uuid.uuid4().hex[:8]}@test.com"
r = api.post("/users", json={"name": name, "email": email, "role": role})
assert r.status_code == 201
user = r.json()
created_ids.append(user["id"])
return user
yield _create
# Cleanup: delete all created users
for uid in created_ids:
api.delete(f"/users/{uid}")
Test Examples
# test_users.py
def test_create_user(api, create_user):
user = create_user(name="Alice", role="admin")
assert user["name"] == "Alice"
assert user["role"] == "admin"
assert "id" in user
def test_get_user(api, create_user):
user = create_user(name="Bob")
r = api.get(f"/users/{user['id']}")
assert r.status_code == 200
assert r.json()["name"] == "Bob"
def test_update_user(api, create_user):
user = create_user(name="Original")
r = api.put(f"/users/{user['id']}", json={
"name": "Updated",
"email": user["email"],
"role": user["role"]
})
assert r.status_code == 200
assert r.json()["name"] == "Updated"
def test_delete_user(api, create_user):
user = create_user()
r = api.delete(f"/users/{user['id']}")
assert r.status_code == 204
# Verify deletion
r = api.get(f"/users/{user['id']}")
assert r.status_code == 404
@pytest.mark.parametrize("email,expected", [
("valid@test.com", 201),
("", 400),
("not-an-email", 422),
("a" * 300 + "@test.com", 422),
])
def test_create_user_email_validation(api, email, expected):
r = api.post("/users", json={"name": "Test", "email": email, "role": "viewer"})
assert r.status_code == expected