Search code examples
djangopytestpytest-django

pytest + django giving me a database error when fixture scope is 'module'


I have the following inside conftest.py

@pytest.mark.django_db
@pytest.fixture(scope='module')
def thing():
    print('sleeping')  # represents a very expensive function that i want to only ever once once per module
    Thing.objects.create(thing='hello')
    Thing.objects.create(thing='hello')
    Thing.objects.create(thing='hello')

Inside tests.py

@pytest.mark.django_db
def test_thing(thing):
    assert models.Thing.objects.count() > 1

@pytest.mark.django_db
def test_thing2(thing):
    assert models.Thing.objects.count() > 1


@pytest.mark.django_db
@pytest.mark.usefixtures('thing')
def test_thing3():
    assert models.Thing.objects.count() > 1

All three tests throw the same error: RuntimeError: Database access not allowed, use the "django_db" mark, or the "db" or "transactional_db" fixtures to enable it.

I've tried using scope='session' / scope='class' / scope='package' / scope='module' -- the only one that works is `scope='function' which defeats the purpose of what I'm trying to accomplish. I want to be able to create all these items ONCE per module, not once per test.

Note: I ran into this issue with a large code base and created a new django project with a single app to test and see if the problem was the existing test code, and it failed on a standalone test also. Tested it with both postgres and sqlite; doesn't seem like a database issue.

Not that it matters, but the models.py

class Thing(models.Model):
    thing = models.CharField(max_length=100)

Solution

  • Ok, turns out this is a known limitation, and it's somewhat documented here. If you want to solve this issue, and get away from this bug:

    @pytest.mark.django_db
    @pytest.fixture(scope='module')
    def thing(django_db_setup, django_db_blocker):
        del django_db_setup  # Cannot be used with usefixtures(..) it won't work
        with django_db_blocker.unblock():
            print('sleeping')
            Thing.objects.create(thing='hello')
            Thing.objects.create(thing='hello')
            Thing.objects.create(thing='hello')
            Thing.objects.create(thing='hello')
            yield