pytest ScopeMismatch error: how to use fixtures properly

The db fixture has the function scope for a reason, so the transaction rollbacks on the end of each test ensure the database is left in the same state it has when test starts. Nevertheless, you can have the session/module scoped access to database in fixture by using the django_db_blocker fixture:

@pytest.fixture(scope="module")
def get_all_models(django_db_blocker):
    with django_db_blocker.unblock():
        return MyModel.objects.all()

Warning

Beware that when unlocking the database in session scope, you’re on your own if you alter the database in other fixtures or tests. In the example below I create an entity of Foo in a session-scoped fixture create_foo, then cache the queryset for session in all_foos:

# models.py

from django.db import models

class Foo(models.Model):
    name = models.CharField(max_length=16)

# test_foo.py

import pytest
from app.models import Foo

@pytest.fixture(scope="session", autouse=True)
def create_foo(django_db_blocker):
    with django_db_blocker.unblock():
        Foo.objects.create(name="bar")


@pytest.fixture(scope="module")
def all_foos(django_db_blocker):
    with django_db_blocker.unblock():
        yield Foo.objects.all()


def test_1(all_foos):
    assert all_foos.exists()

def test_2(all_foos, db):
    all_foos.delete()
    assert not Foo.objects.exists()

def test3(all_foos):
    assert all_foos.exists()

After the test_2 runs, the queryset stored in session from all_foos will be empty, causing test_3 to fail:

test_foo.py::test_1 PASSED                                                           [ 33%]
test_foo.py::test_2 PASSED                                                           [ 66%]
test_foo.py::test_3 FAILED                                                           [100%]

========================================= FAILURES ========================================
__________________________________________ test_3 _________________________________________

all_foos = <QuerySet []>

    def test_3(all_foos):
>       assert all_foos.exists()
E       assert False
E        +  where False = <bound method QuerySet.exists of <QuerySet []>>()
E        +    where <bound method QuerySet.exists of <QuerySet []>> = <QuerySet []>.exists

test_foo.py:28: AssertionError

Consequence: never store references in session scope if you don’t want to introduce a global state that can change in tests. Query the data from database and return copies or serialized data, and so on.

Example for a safe usage:

@pytest.fixture(scope="session")
def foo_names(django_db_blocker):
    with django_db_blocker.unblock():
        names = list(Foo.objects.values_list('name', flat=True))
    return names

Leave a Comment