FastAPI для начинающих: API с базой данных и тестамиТесты вместо ручного прокликиванияFixtures, временная база и dependency override

Fixtures, временная база и dependency override

Уроки курсаFixtures, временная база и dependency override

Отдельный SQLite-engine и отсутствие SQL при импорте

Чтобы тест работал с отдельной базой, нужно решить одну проблему заранее: в типичном проекте create_all вызывается при импорте модуля, и pytest, собирая тесты, уже создаёт рабочий файл базы. Нужно убрать этот побочный эффект.

Подготовка папки

Создайте папку testing_demo и скопируйте в неё все Python-файлы сохраняющего API из урока 26: models.py, database.py, schemas.py, booking_schemas.py, booking_utils.py, main.py. Файлы *.db не копируйте. Затем замените database.py полностью — файл ниже отличается от оригинала в двух ключевых местах.

# database.py
from sqlalchemy import create_engine, event
from sqlalchemy.orm import sessionmaker
from models import Base

DATABASE_URL = "sqlite:///./roomly_intervals.db"

def create_db_engine(url):
    configured_engine = create_engine(url, connect_args={"check_same_thread": False})

    @event.listens_for(configured_engine, "connect")
    def enable_foreign_keys(connection, connection_record):
        cursor = connection.cursor()
        cursor.execute("PRAGMA foreign_keys=ON")
        cursor.close()

    return configured_engine

engine = create_db_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

def init_db():
    Base.metadata.create_all(bind=engine)

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

Что изменилось и почему

Первое изменение: create_all убран из верхнего уровня модуля и перенесён в функцию init_db. create_engine создаёт ленивый объект — он не открывает файл и не выполняет SQL. Таблицы появятся только при явном вызове init_db. Когда pytest импортирует main, файл roomly_intervals.db не создаётся.

Второе изменение: логика создания engine вынесена в функцию create_db_engine. Это нужно fixture: ей потребуется свой engine с тем же PRAGMA-слушателем, но для другого файла. SQLAlchemy не переносит слушателей событий автоматически — каждый engine настраивается отдельно. Вызов create_db_engine с другим URL даст полноценный тестовый engine с включёнными внешними ключами.

get_db остался прежним: зависимость открывает Session из SessionLocal, выдаёт её обработчику и закрывает в finally. Именно эту функцию fixture заменит в тестах.

Запуск вручную

Для обычного ручного запуска копии сначала создайте схему:

python -c "from database import init_db; init_db()"
python -m uvicorn main:app --reload

Тесты эту команду не используют — они создают схему сами в отдельном файле.

create_all создаёт таблицы, которых ещё нет в указанной базе. Он не добавляет столбцы в уже существующие таблицы и не трогает другие базы.