Search code examples
pythondjangopytestpytest-django

How to reuse database in some tests using `pytest-django`?


According to the documentation, --reuse-db should be used or addopts = --reuse-db in pytest.ini. I tried both and they don't work. The current tests have to signup and authenticate a new user at the start of each test to be able to access features requiring login. This makes tests run slowly and with the number of tests growing, this is becoming less convenient. Here's an example to demonstrate:

@pytest.mark.django_db
class TestSignIn:
    def test_valid_user(self, client, valid_user_data, django_user_model, django_db_keepdb):
        client_signup(client, valid_user_data, django_db_keepdb)
        response = activate_user(client)
        assert 'sign out' in get_text(response)

    def test_sign_in(self, client, valid_user_data, django_user_model, django_db_keepdb):
        client_signup(client, valid_user_data)
        activate_user(client)
        response = client.post(reverse('signout'), follow=True)
        assert 'sign in' in get_text(response)
        response = client_sign_in(client, valid_user_data)
        assert 'sign out' in get_text(response)

In test_valid_user, a new user is created and after user activation, a user shows up:

>>> django_user_model.objects.count()
1

and I verify using:

>>> django_db_keepdb
True

I want to reuse the user created in the previous test instead of creating a new one. With --reuse-db specified, I'm expecting the second test test_sign_in to detect the very same user. Instead, I get:

>>> django_user_model.objects.count()
0

Solution

  • @pytest.mark.django_db marker runs the function-scoped _django_db_helper fixture.

    Implement @pytest.mark.django_db_class_scope marker that runs a class-scoped fixture:

    import pytest
    from pytest_django.fixtures import _django_db_helper
    
    _django_db_function_scope_helper = pytest.fixture(_django_db_helper.__wrapped__, scope='function')
    _django_db_class_scope_helper = pytest.fixture(_django_db_helper.__wrapped__, scope='class')
    
    
    @pytest.fixture()
    def _django_db_helper(request) -> None:
        marker = request.node.get_closest_marker('django_db_class_scope')
        if not marker:
            request.getfixturevalue('_django_db_function_scope_helper')
    
    
    @pytest.fixture(autouse=True)
    def django_db_class_scope_marker(request) -> None:
        marker = request.node.get_closest_marker('django_db_class_scope')
        if marker:
            request.getfixturevalue('_django_db_class_scope_helper')
    

    Usage:

    # @pytest.mark.django_db
    @pytest.mark.django_db_class_scope
    class TestSignIn:
        def test_valid_user(self, django_user_model):
            django_user_model.objects.create()
            assert django_user_model.objects.count() == 1
    
        def test_sign_in(self, django_user_model):
            assert django_user_model.objects.count() == 1