pytest-dev / pytest-django

A Django plugin for pytest.
https://pytest-django.readthedocs.io/
Other
1.38k stars 344 forks source link

django_db_block access wrong database bug #884

Open lieric7766 opened 3 years ago

lieric7766 commented 3 years ago

when i tried this command pytest -s --import-mode=importlib it works fine. but when i tried this pytest -s --import-mode=importlib app/tests/test_filename.py first time was a general fail output but after that i got this error:

===================================================================================== ERRORS =====================================================================================
_________________________________________________________________ ERROR at setup of test_endpoint_containerList __________________________________________________________________

self = <django.db.backends.utils.CursorWrapper object at 0x107070190>
sql = 'INSERT INTO "auth_user" ("password", "last_login", "is_superuser", "username", "first_name", "last_name", "email", "is_staff", "is_active", "date_joined") VALUES (%s, %s, 
%s, %s, %s, %s, %s, %s, %s, %s) RETURNING "auth_user"."id"'             
params = ('pbkdf2_sha256$180000$UlJQcZHRuts0$jpA8wYudq5I+QdAPXWe6lvqU7V4t3CvADtn4iXpfR64=', None, False, 'test', '', '', ...)
ignored_wrapper_args = (False, {'connection': <django.db.backends.postgresql.base.DatabaseWrapper object at 0x1068fd290>, 'cursor': <django.db.backends.utils.CursorWrapper object
 at 0x107070190>})                                                                                                                                                                

    def _execute(self, sql, params, *ignored_wrapper_args):                                                                                                                       
        self.db.validate_no_broken_transaction()
        with self.db.wrap_database_errors:                                                                                                                                        
            if params is None:             
                # params default might be backend specific.                                                                                                                       
                return self.cursor.execute(sql)                
            else:                                                                                                                                                                 
>               return self.cursor.execute(sql, params)
E               psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint "auth_user_username_key"
E               DETAIL:  Key (username)=(test) already exists.                                                                                                                                                                                                             
../../.pyenv/versions/3.7.4/envs/dsl/lib/python3.7/site-packages/django/db/backends/utils.py:86: UniqueViolation             
                                                                                                                                                                                  The above exception was the direct cause of the following exception:

django_db_blocker = <pytest_django.plugin._DatabaseBlocker object at 0x105377f90>

    @pytest.fixture(scope='session')      
    def test_client(django_db_blocker):
        with django_db_blocker.unblock():                                                
>           User.objects.create_user('test', 'test@test.com', 'test123')

conftest.py:20:

i checked my postgres database test_dbname first and select auth_user this table and it was empty i couldn't find this user i saved. i found it insert to my original database dbname auth_user table. it only occurred when i test specific test file

my conftest.py

@pytest.fixture(scope='session')
def test_client(django_db_blocker):
    with django_db_blocker.unblock():
        User.objects.create_user('test', 'test@test.com', 'test123')
        client = APIClient()
        client.login(username='test', password='test123')

    return client

my project structure

│   conftest.py    
│
└───app_folder
│   │   apps.py
│   │   models.py
│   │   ...
│   └───tests
│       │   test_file1.py
│       │   test_file2.py
jefftriplett commented 2 years ago

Running into this too and I was curious if anyone found a workaround for it?

archmatters commented 2 years ago

For anyone else encountering this, there seems to be some magic around having a django_db_setup fixture, which probably should be defined in a conftest.py at project root. See https://pytest-django.readthedocs.io/en/latest/database.html#populate-the-database-with-initial-test-data for an example using django_db_setup...

If I have session-scoped fixtures using django_db_blocker.unblock() without a django_db_setup fixture calling unblock() first, I see those other fixtures using unblock() hitting the wrong database as well. But once I add a django_db_setup fixture, everything goes to the test database. Now, to ensure this gets called first, I think you will need every other session scoped fixture wanting database access to request it. And, you will have data persisting in the DB if you don't add teardown to clean it up (or, I suppose, drop the test database after every session).

For example: conftest.py

import pytest
from myapp.models import User

@pytest.fixture(scope="session")
def django_db_setup(django_db_setup, django_db_blocker):
    """
    Tie to test database, cleanup at the end.
    """
    with django_db_blocker.unblock():
        yield
        User.objects.all().delete()

myapp/tests/fixtures.py

import pytest
from myapp.models import User

@pytest.fixture(scope="session")
def test_user(django_db_setup, django_db_blocker):
    with django_db_blocker.unblock():
        return User.objects.create_user( ... )

And yes, having the django_db_setup fixture request itself seems to be part of the black magic here. I don't understand how this works to ensure the correct database is used, but it works with my project. I hope this becomes simpler and more straightforward with some of the work to resolve #514 !