FastAPI + pytest не могут очистить Django ORM

Я создаю проект FastAPI, который интегрируется с Django ORM. При запуске pytest база данных PostgreSQL не откатывает транзакции. При переходе на SQLite база данных SQLite не очищает транзакции, но сносит базу данных (возможно, потому что SQLite использует in-memory db). Я полагаю, что pytest-django не вызывает метод отката для очистки базы данных.

В моем pytest.ini у меня включен флаг --reuse-db.

Вот репозиторий: https://github.com/Andrew-Chen-Wang/fastapi-django-orm, который включает pytest-django и pytest-asyncio

Предполагается, что у вас есть PostgreSQL:

Шаги для воспроизведения:

  1. sh bin/create_db.sh, который создает новую базу данных под названием testorm
  2. pip install -r requirements/local.txt
  3. pytest tests/

Тест вызывает представление, которое создает новую запись в таблицах базы данных и проверяет, увеличивается ли количество строк в таблице:

# In app/core/api/a_view.py
@router.get("/hello")
async def hello():
    await User.objects.acreate(name="random")
    return {"message": f"Hello World, count: {await User.objects.acount()}"}


# In tests/conftest.py
import pytest
from httpx import AsyncClient

from app.main import fast

@pytest.fixture()
def client() -> AsyncClient:
    return AsyncClient(app=fast, base_url="http://test")

# In tests/test_default.py
async def test_get_hello_view(client):
    """Tests whether the view can use a Django model"""
    old_count = await User.objects.acount()
    assert old_count == 0
    async with client as ac:
        response = await ac.get("/hello")
    assert response.status_code == 200
    new_count = await User.objects.acount()
    assert new_count == 1
    assert response.json() == {"message": "Hello World, count: 1"}

async def test_clears_database_after_test(client):
    """Testing whether Django clears the database"""
    await test_get_hello_view(client)

Первый тест проходит, а второй - нет. При повторном запуске pytest первый тест также начинает не проходить, потому что тестовая база данных не очищает транзакцию с первого запуска.

Я скорректировал тест, чтобы не включать вызов клиента, но похоже, что pytest-django просто не создает транзакцию вокруг Django ORM, потому что БД не очищается для каждого теста:

async def test_get_hello_view(client):
    """Tests whether the view can use a Django model"""
    old_count = await User.objects.acount()
    assert old_count == 0
    await User.objects.acreate(name="test")
    new_count = await User.objects.acount()
    assert new_count == 1


async def test_clears_database_after_test(client):
    """Testing whether Django clears the database"""
    await test_get_hello_view(client)

Как я должен очищать базу данных для каждого тестового случая?

@pytest.fixture()
def django_db_setup():
    from django.db import transaction
    from django.test.utils import CaptureQueriesContext
    from django.test.utils import ConnectionHandler

    connection = ConnectionHandler(settings.DATABASES)
    connection.allow_thread_sharing = True
    conn = connection.use_this_connection()
    with transaction.atomic(using=conn.alias):
        with CaptureQueriesContext(conn) as context:
            yield context
    transaction.set_rollback(True, using=conn.alias)

async def test_get_hello_view(client, django_db_setup):
    old_count = await User.objects.acount()
    assert old_count == 0
    await User.objects.acreate(name="test")
    new_count = await User.objects.acount()
    assert new_count == 1

Похоже, что проблема в том, что pytest-django не создает транзакцию вокруг вызовов Django ORM, поэтому база данных не очищается между тестовыми ситуациями. Один из способов решить эту проблему - создать фикстуру, которая обернёт каждый тестовый пример в транзакцию и вручную откинет её в конце теста. Это создаст новую транзакцию для каждого тестового случая и откинет ее в конце теста, что очистит базу данных для каждого теста. Другой вариант - использовать команду django_db_reset_sequences из pytest-django, которая возвращает автоинкрементирующие первичные ключи к их первоначальному значению. Также следует проверить, не вызывает ли проблему флаг '--reuse-db' в pytest.ini. Он используется для того, чтобы не создавать новую базу данных для каждого запуска теста, попробуйте убрать его и посмотрите, сохранится ли проблема.

Оказывается, в моем pytest.mark.django_db просто нужно было указать transaction=True, как pytest.mark.django_db(transaction=True)

Вернуться на верх