Тесты вместо ручного прокликивания

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

Содержание курса

Override get_db: отдельная Session на каждый запрос и восстановление после теста

Теперь есть функция create_db_engine, которую можно вызвать с путём к временному файлу. Осталось сказать FastAPI: «для тестов не вызывай get_db из database.py — вместо неё используй вот эту функцию». Это делает app.dependency_overrides.

Создайте рядом с main.py файл conftest.py — pytest автоматически загружает его перед тестами и делает все объявленные в нём fixtures доступными без явного импорта.

# conftest.py
import pytest
from fastapi.testclient import TestClient
from sqlalchemy.orm import sessionmaker
from main import app
from database import create_db_engine, get_db
from models import Base

@pytest.fixture
def client(tmp_path):
    database_path = tmp_path / "test.db"
    test_engine = create_db_engine(f"sqlite:///{database_path.as_posix()}")
    previous_overrides = dict(app.dependency_overrides)
    try:
        Base.metadata.create_all(bind=test_engine)
        TestingSession = sessionmaker(bind=test_engine, autocommit=False, autoflush=False)

        def override_get_db():
            db = TestingSession()
            try:
                yield db
            finally:
                db.close()

        app.dependency_overrides[get_db] = override_get_db
        with TestClient(app) as test_client:
            yield test_client
    finally:
        app.dependency_overrides.clear()
        app.dependency_overrides.update(previous_overrides)
        test_engine.dispose()
        database_path.unlink(missing_ok=True)

tmp_path — встроенная fixture pytest: она даёт отдельный временный каталог для каждого теста. Файл test.db внутри этого каталога живёт ровно столько, сколько нужно тесту.

previous_overrides = dict(app.dependency_overrides) сохраняет снимок текущего словаря. Это важно: если в проекте уже есть другие подмены (например, для конфигурации), их нельзя потерять и нельзя удалить чужой подменой. В finally словарь восстанавливается двумя строками: сначала очищается, потом заполняется сохранённым снимком.

app.dependency_overrides[get_db] = override_get_db — ключ здесь именно объект функции get_db, тот же самый, который передаётся в Depends внутри обработчиков. Строка "get_db" или вызов get_db() не сработают.

override_get_db открывает новую Session на каждый входящий запрос. Все запросы одного теста видят один и тот же файл базы, но каждый запрос получает свою Session — так же, как в боевом get_db.

Порядок завершения в finally важен: сначала завершается контекст TestClient, потом восстанавливаются overrides, потом test_engine.dispose() закрывает пул соединений engine, и только после этого удаляется файл. На Windows открытый файл базы нельзя удалить — dispose обязателен перед unlink.

Тесты

# test_rooms.py
def test_create_room(client):
    response = client.post("/api/rooms", json={"name": "Альфа", "capacity": 4})
    assert response.status_code == 201
    room = response.json()
    assert type(room["id"]) is int
    assert room["name"] == "Альфа"
    assert room["capacity"] == 4
    response = client.get("/api/rooms")
    assert response.status_code == 200
    assert response.json() == [room]

def test_empty_rooms(client):
    response = client.get("/api/rooms")
    assert response.status_code == 200
    assert response.json() == []

test_create_room создаёт комнату и сразу проверяет, что GET возвращает её. test_empty_rooms проверяет, что база пуста. Эти два теста проходят в любом порядке: каждый получает свою базу от своего запуска fixture, и запись из одного теста физически не существует в файле другого.

Запуск из папки testing_demo в том же окружении, где установлены pytest и httpx:

python -m pytest -q test_rooms.py

Оба теста пройдут. Файл roomly_intervals.db не появится. Если он существовал до запуска тестов, его содержимое останется нетронутым.

Два независимых запуска fixture client со scope function. В тесте A POST создаёт запись, а последующий GET того же теста видит её. В тесте B первый GET возвращает пустой список. Оба файла называются test.db, но находятся в разных tmp_path; обычная база проекта не открывается. Различие обеспечивают отдельный engine и override get_db, а не само создание TestClient.
Запросы одного теста используют его временную базу. Другой тест получает новый файл через отдельный запуск fixture.

Начать заново?

Помощник начнёт новый диалог по этой задаче. Текущая переписка останется в истории до конца срока хранения.

О переписке

Можно приложить до трёх фото PNG, JPEG или WebP по 5 МБ.

Переписка и фото хранятся 90 дней после последнего принятого сообщения. Преподаватель получит доступ к диалогу, когда вы попросите его помочь.

Проверка решения остаётся отдельным действием.