Skip to content

Testing

P8s provides Django-style testing utilities for FastAPI applications.

Overview

from p8s.testing import TestClient, RequestFactory
from p8s.testing import assert_status_code, assert_json_contains, assert_redirect

TestClient

Wrapper around Starlette's TestClient:

from p8s.testing import TestClient
from backend.main import app

client = TestClient(app)

def test_health_endpoint():
    response = client.get("/health")
    assert response.status_code == 200
    assert response.json()["status"] == "healthy"

RequestFactory

Create mock requests for unit testing:

from p8s.testing import RequestFactory

factory = RequestFactory()

# GET request
request = factory.get("/products", params={"page": 1})

# POST request with JSON
request = factory.post("/products", json={"name": "Widget"})

# With authentication
request = factory.get("/api/me", headers={"Authorization": "Bearer token123"})

# Custom defaults
factory = RequestFactory(headers={"X-API-Key": "secret"})

Factory Methods

Method Description
factory.get(path, **kwargs) Create GET request
factory.post(path, **kwargs) Create POST request
factory.put(path, **kwargs) Create PUT request
factory.patch(path, **kwargs) Create PATCH request
factory.delete(path, **kwargs) Create DELETE request

Assertions

assert_status_code

from p8s.testing import assert_status_code

response = client.get("/products")
assert_status_code(response, 200)
# Raises AssertionError with helpful message if status doesn't match

assert_json_contains

from p8s.testing import assert_json_contains

response = client.get("/products/1")
assert_json_contains(response, "name")  # Check key exists
assert_json_contains(response, "name", "Widget")  # Check value

assert_redirect

from p8s.testing import assert_redirect

response = client.get("/old-url", follow_redirects=False)
assert_redirect(response, "/new-url")

Example Test File

# tests/test_products.py
import pytest
from p8s.testing import TestClient, assert_status_code, assert_json_contains
from backend.main import app

@pytest.fixture
def client():
    return TestClient(app)

class TestProductAPI:
    def test_list_products(self, client):
        response = client.get("/api/products")
        assert_status_code(response, 200)
        assert isinstance(response.json(), list)

    def test_create_product(self, client):
        response = client.post("/api/products", json={
            "name": "Test Product",
            "price": 9.99,
        })
        assert_status_code(response, 201)
        assert_json_contains(response, "id")
        assert_json_contains(response, "name", "Test Product")

    def test_get_product_not_found(self, client):
        response = client.get("/api/products/nonexistent-id")
        assert_status_code(response, 404)

Running Tests

# Run all tests
pytest tests/

# Verbose output
pytest tests/ -v

# Specific file
pytest tests/test_products.py

# With coverage
pytest tests/ --cov=backend

Async Tests

For async test functions:

pip install pytest-asyncio
import pytest

@pytest.mark.asyncio
async def test_async_operation():
    result = await some_async_function()
    assert result == expected

Database Testing

For tests that need a clean database:

import pytest
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlmodel import SQLModel

@pytest.fixture
async def db_session():
    engine = create_async_engine("sqlite+aiosqlite:///:memory:")
    async with engine.begin() as conn:
        await conn.run_sync(SQLModel.metadata.create_all)

    async with AsyncSession(engine) as session:
        yield session

    await engine.dispose()

async def test_create_user(db_session):
    from backend.apps.users.models import User

    user = User(email="test@example.com", password_hash="...")
    db_session.add(user)
    await db_session.commit()

    assert user.id is not None