dependency_overrides не меняет основную базу данных на тестовую

Project structure:

project
├── app
│   ├── __init__.py
│   ├── main.py
│   └── routers
│       ├── __init__.py
│       └── user.py
├── tests
│   ├── __init__.py
│   ├── test_user.py
│   └── database_test.py
└── docker-compose.yaml

database.py:

from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import declarative_base, sessionmaker

Base = declarative_base()

DATABASE_URL = "postgresql+asyncpg://admin:admin@postgres_cont"

engine = create_async_engine(DATABASE_URL, echo=True)

async_session = sessionmaker(
    engine, expire_on_commit=False, class_=AsyncSession
)  # type: ignore

main.py:

import logging
import os
from contextlib import asynccontextmanager

from fastapi import Depends, FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates

from src.app.database import Base, async_session, engine
from src.app.routes import follow, media, tweets, user

logging.basicConfig(level="DEBUG")
logger = logging.getLogger(__name__)


async def get_session():
    async with async_session() as s:
        yield s


DEFAULT_SESSION = Depends(get_session)


@asynccontextmanager
async def lifespan(application: FastAPI):
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
    yield
    await engine.dispose()


app = FastAPI(lifespan=lifespan)
app.include_router(user.router)

user.py

import logging
from typing import List, Optional, Union

from fastapi import APIRouter, Depends, Header
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from src.app.database import async_session
from src.app.models import User
from src.app.schemas import ErrorMessageSchema, UserInSchema, UserOutSchema

logging.basicConfig(level="DEBUG")
logger = logging.getLogger(__name__)


async def get_session():
    async with async_session() as s:
        yield s


DEFAULT_SESSION = Depends(get_session)


router = APIRouter()

@router.get(
    "/api/users/{user_id}", response_model=Union[UserOutSchema, ErrorMessageSchema]
)
@router.get("/api/users/me", response_model=Union[UserOutSchema, ErrorMessageSchema])
async def get_user(
    session: AsyncSession = DEFAULT_SESSION,
    api_key: Optional[str] = Header(None, alias="api-key"),
    user_id: Optional[int] = None,
):
    if user_id:
        logger.info("We get user_id")
        res = await session.execute(select(User).filter(User.id == user_id))

    elif api_key:
        logger.info("We get api_key")
        res = await session.execute(select(User).filter(User.api_key == api_key))

    else:
        logger.error("We didn't get anything")
        return {
            "result": False,
            "error_type": "A required param is missed",
            "error_message": "Not found api_key or user_id",
        }

    logger.info("Trying to get user...")
    result_row = res.one_or_none()
    if result_row is None:  
        logger.error("We didn't get a user")
        if user_id is not None:
            error_msg = f"Not found user with id {user_id}"
        else:
            error_msg = "Not found user with this api_key"
        return {
            "result": False,
            "error_type": "User not found",
            "error_message": error_msg,
        }

    logger.info("We got a user!")
    result: User = result_row[0] 
    return result.to_json()

database_test.py:

from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import declarative_base, sessionmaker

Base = declarative_base()

DATABASE_URL = "postgresql+asyncpg://admin:admin@test_postgres_cont:5432"

engine_test = create_async_engine(DATABASE_URL, echo=True)

async_session_test = sessionmaker(
    engine_test, expire_on_commit=False, class_=AsyncSession
)  # type: ignore

test_user.py:

import pytest
import pytest_asyncio
from httpx import AsyncClient
from asgi_lifespan import LifespanManager

from src.app.main import app, get_session
from tests.database_test import engine_test, Base, async_session_test
from src.app.models import User


async def override_get_async_session():
    async with async_session_test() as session:
        yield session


app.dependency_overrides[get_session] = override_get_async_session


@pytest.fixture(scope="module", autouse=True)
async def setup_database():
    async with engine_test.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)

    async with async_session_test() as session:
        async with session.begin():
            user_1 = User(name="Anton", api_key="look")
            user_2 = User(name="Grisha", api_key="qwert")
            session.add(user_1)
            session.add(user_2)

    yield
    async with engine_test.begin() as conn:
        await conn.run_sync(Base.metadata.drop_all)


@pytest_asyncio.fixture
async def async_client():
    async with LifespanManager(app):
        async with AsyncClient(app=app, base_url="http://test") as ac:
            yield ac


@pytest.mark.asyncio
async def test_user_by_id(async_client):
    response = await async_client.get("/api/users/1")

    assert response.status_code == 200
    assert response.json() == {
        "id": 1,
        "name": "Anton",
        "following": [],
        "followers": []
    }

docker-compose.yaml:

services:
  app:
    container_name: fastapi_cont
    build:
      context: .
    depends_on:
      - postgres
    stop_signal: SIGKILL
    ports:
      - "8000:8000"
    command: gunicorn --bind=0.0.0.0:8000 main:app --workers=4 --log-level debug -k uvicorn.workers.UvicornWorker --timeout 200
    networks:
      - backend_network

  postgres:
    image: postgres
    container_name: postgres_cont
    environment:
      - POSTGRES_USER=admin
      - POSTGRES_PASSWORD=admin
      - PG_LOG_DEST=stderr
      - PG_LOG_COLLECTOR=on
      - PG_LOG_DIR=pg_log
      - PG_LOG_FILE=postgresql-%Y-%m-%d_%H%M%S.log
    ports:
      - '5432:5432'
    volumes:
      - ./db/:/var/lib/postgresql/data
    networks:
      - backend_network

  test_db:
    image: postgres
    container_name: test_postgres_cont
    environment:
      - POSTGRES_USER=admin
      - POSTGRES_PASSWORD=admin
    ports:
      - "5433:5432"  # Внешний 5433 -> внутренний 5432
    volumes:
      - ./test_db/:/var/lib/postgresql/data
    networks:
      - backend_network

  tests:
    build:
      context: .
    working_dir: /tests
    depends_on:
      - test_db
      - app
    environment:
      DATABASE_URL: postgresql+asyncpg://admin:admin@test_postgres_cont:5432
      PYTHONPATH: /src/app
    command: pytest --asyncio-mode=auto
    networks:
      - backend_network


networks:
  backend_network:

Ответы (1 шт):

Автор решения: Hinokas

в docker-compose прописать:

app:
 environment:
  DATABASE_URL: postgresql+asyncpg://admin:admin@postgres_cont:5432
tests:
 environment:
  DATABASE_URL: postgresql+asyncpg://admin:admin@test_postgres_cont:5432

и везде в DATABASE_URL прописывать: os.getenv("DATABASE_URL")

→ Ссылка