pytest ScopeMismatch error: how to use fixtures properly

The name of the pictureThe name of the pictureThe name of the pictureClash 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.

Popular posts from this blog

Firebase Auth - with Email and Password - Check user already registered

Dynamically update html content plain JS

How to determine optimal route across keyboard