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")