DEV Community

nicolas.vbgh
nicolas.vbgh

Posted on

Backend Tests

Part of The Coercion Saga — making AI write quality code.

The Behavior Gate

Quality gates catch syntax. Tests catch behavior. A function that type-checks but returns garbage is worse than one that fails to compile.


The Async Trap

FastAPI is async. Get testing wrong and tests hang or miss real bugs.

# conftest.py
import asyncio
import pytest

@pytest.fixture(scope="session")
def event_loop():
    loop = asyncio.new_event_loop()
    loop.set_debug(True)  # Catches blocking calls
    yield loop
    loop.close()
Enter fullscreen mode Exit fullscreen mode

set_debug(True) catches time.sleep() instead of asyncio.sleep(), synchronous I/O, libraries that claim async but aren't. One line, whole category of bugs.


Test Client

@pytest.fixture
async def client(session: AsyncSession) -> AsyncGenerator[AsyncClient, None]:
    app.dependency_overrides[get_session] = lambda: session
    async with AsyncClient(app=app, base_url="http://test") as client:
        yield client
    app.dependency_overrides.clear()
Enter fullscreen mode Exit fullscreen mode

Endpoint code doesn't know it's being tested. Runs exactly like production.


What to Test

Service layer:

async def test_create_user(session: AsyncSession):
    user = await create_user(session, email="test@example.com", password="secret")
    assert user.password != "secret"  # Should be hashed
Enter fullscreen mode Exit fullscreen mode

Error cases:

async def test_register_duplicate(client: AsyncClient, session: AsyncSession):
    await create_user(session, email="taken@example.com", password="secret")
    await session.commit()

    response = await client.post("/auth/register", json={
        "email": "taken@example.com", "password": "different"
    })
    assert response.status_code == 409  # Not 500
Enter fullscreen mode Exit fullscreen mode

409 Conflict means you caught it intentionally. 500 means you caught it in a crash.


The Gate

test:backend:
  stage: test
  image: python:3.12-slim
  services:
    - postgres:16
  variables:
    DATABASE_URL: postgresql+asyncpg://postgres:postgres@db:5432/test
  before_script:
    - pip install uv && cd backend && uv sync --frozen
    - uv run alembic upgrade head
  script:
    - uv run pytest -v --cov=app --cov-report=term-missing --cov-report=html
  allow_failure: false
Enter fullscreen mode Exit fullscreen mode

Copy, paste, adapt. It works.


The Point

Tests are the safety net. Refactor with confidence. Ship knowing the code works.

That's the deal.


Next up: [Frontend Tests] -coming soon- — Now prove the UI works too.

Top comments (0)