FastAPI + pytest unable to clean Django ORM

I'm creating a FastAPI project that integrates with the Django ORM. When running pytest though, the PostgreSQL database is not rolling back the transactions. Switching to SQLite, the SQLite database is not clearing the transactions, but it is tearing down the db (probably because SQLite uses in-memory db). I believe pytest-django is not calling the rollback method to clear the database.

In my pytest.ini, I have the --reuse-db flag on.

Here's the repo: https://github.com/Andrew-Chen-Wang/fastapi-django-orm which includes pytest-django and pytest-asyncio

Assuming you have PostgreSQL:

Steps to reproduce:

  1. sh bin/create_db.sh which creates a new database called testorm
  2. pip install -r requirements/local.txt
  3. pytest tests/

The test is calling a view that creates a new record in the database tables and tests whether there is an increment in the number of rows in the table:

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

The first test case passes but the second doesn't. If you re-run pytest, the first test case also starts not passing because the test database is not clearing the transaction from the first run.

I adjusted the test to not include the client call, but it seems like pytest-django is simply not creating a transaction around the Django ORM, because the db is not being cleared for each test:

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)

How should I clear the database for each test case?

@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

It seems like the problem is that pytest-django isn't creating a transaction around the Django ORM calls, so the database isn't being cleared between test cases. One way to fix this is to create a fixture that wraps each test case in a transaction and manually rolls it back at the end of the test. This will create a new transaction for each test case and roll it back at the end of the test, which should clear the database for each test. Another option is to use pytest-django's django_db_reset_sequences, which resets the auto-incrementing primary keys back to their original value. Also, you should check if the '--reuse-db' flag in pytest.ini is causing the problem. It's used to not create a new database for each test run, try removing it and see if the issue still exists.

Turns out my pytest.mark.django_db just needed transaction=True in it like pytest.mark.django_db(transaction=True)

Back to Top