Testing API services is often postponed until bugs hit staging or production. In Python, pytest is the standard testing framework. It makes writing clean, expressive unit and integration tests fast through powerful features like fixtures and parametrized tests.
This guide highlights what to test first to get maximum coverage with minimal test maintenance friction.
1. The Test Pyramid for APIs
- Unit Tests (High volume): Test isolated domain functions, Pydantic data transformations, and business rule algorithms.
- Integration Tests (Medium volume): Test API endpoints using an HTTP test client (like HTTPX or FastAPI TestClient) against a test database.
- End-to-End Tests (Low volume): Test critical end-to-end flows including external webhooks or third-party gateways.
2. Leveraging Pytest Fixtures
Fixtures provide reusable context (like database connections or mock objects) across test cases cleanly.
# conftest.py
import pytest
from httpx import AsyncClient
from src.my_app.main import app
@pytest.fixture
async def async_client():
async with AsyncClient(app=app, base_url="http://test") as client:
yield client
@pytest.fixture
def sample_user_payload():
return {
"email": "test@example.com",
"username": "testuser",
"age": 25
}
3. Writing an API Integration Test
# tests/test_users.py
import pytest
@pytest.mark.asyncio
async def test_create_user_success(async_client, sample_user_payload):
response = await async_client.post("/api/users", json=sample_user_payload)
assert response.status_code == 201
data = response.json()
assert data["email"] == sample_user_payload["email"]
assert "id" in data
@pytest.mark.asyncio
async def test_create_user_invalid_age(async_client, sample_user_payload):
sample_user_payload["age"] = 15 # Under minimum age 18
response = await async_client.post("/api/users", json=sample_user_payload)
assert response.status_code == 422 # Pydantic validation error
4. Mocking External Services (e.g. OpenAI or Stripe)
Never call real paid third-party APIs during automated test runs. Use unittest.mock or respx to intercept HTTP requests.
from unittest.mock import AsyncMock
@pytest.mark.asyncio
async def test_ai_summary_endpoint(async_client, monkeypatch):
mock_openai = AsyncMock(return_value="This is a mock AI summary.")
monkeypatch.setattr("src.my_app.services.ai_service.generate_summary", mock_openai)
response = await async_client.get("/api/ai-summary?prompt=hello")
assert response.status_code == 200
assert response.json()["summary"] == "This is a mock AI summary."
Summary
Focusing your testing on domain business logic and API endpoints using pytest fixtures creates a safety net that lets you refactor code with complete confidence.
Need help setting up automated testing pipelines for your Python microservices? Learn more on my AI integration page or Hire Python Developer.