pytest ScopeMismatch error: how to use fixtures properly
Clash Royale CLAN TAG#URR8PPP
pytest ScopeMismatch error: how to use fixtures properly
For the following piece of code:
@pytest.fixture(scope="module")
def dummy_article(request, db):
return mixer.blend("core.article", title="this one price", internal_id=3000)
def test_article_str_method(dummy_article):
assert (
str(dummy_article)
== f"article with ID dummy_article.internal_id and title: dummy_article.title"
)
I'm getting the following error:
ScopeMismatch: You tried to access the 'function' scoped fixture 'db' with a 'module' scoped request object, involved factories
core/tests/test_article_model.py:13: def dummy_article(request, db)
The error goes away if I change the fixture to use scope="function"
, but that defeats the purpose of having it available to other tests and not having to "set-up" for every test.
scope="function"
How can I have fixtures with db
access that have a scope wider than function
?
db
function
1 Answer
1
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:
db
function
django_db_blocker
@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
:
Foo
create_foo
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_2
all_foos
test_3
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
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.