Home > OS >  Why is Django test cases checking actual DB and raising IntegrityError instead of just running in-me
Why is Django test cases checking actual DB and raising IntegrityError instead of just running in-me

Time:12-08

When I run my tests with the DB empty (the actual application DB), everything goes fine. But when the DB has data, Django raises an IntegrityError for basically every test. The stact trace looks like the following (but for every test):

======================================================================
ERROR: test_api_get_detail (core.projects.tests.test_project_api.ProjectConfidentialPrivateAPITests)
Getting confidential object via API (GET)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/ramonkcom/Desktop/management/management-backend/venv/lib/python3.10/site-packages/django/test/testcases.py", line 299, in _setup_and_call
    self._post_teardown()
  File "/Users/ramonkcom/Desktop/management/management-backend/venv/lib/python3.10/site-packages/django/test/testcases.py", line 1199, in _post_teardown
    self._fixture_teardown()
  File "/Users/ramonkcom/Desktop/management/management-backend/venv/lib/python3.10/site-packages/django/test/testcases.py", line 1461, in _fixture_teardown
    connections[db_name].check_constraints()
  File "/Users/ramonkcom/Desktop/management/management-backend/venv/lib/python3.10/site-packages/django/db/backends/sqlite3/base.py", line 383, in check_constraints
    raise IntegrityError(
django.db.utils.IntegrityError: The row in table 'custom_fields_customfield' with primary key '1' has an invalid foreign key: custom_fields_customfield.definition_id contains a value '1' that does not have a corresponding value in custom_fields_customfielddefinition.id.

At first I thought this was a problem with my fixtures. But they work just fine to set up the application basic data. The DB seems to be the key issue: if it has data, the tests crash, if it's empty, the tests work.

The most weird part is that the error being raised doesn't match with reality, and by that I mean: the CustomFieldDefinition object with pk=1 exists in the DB. Anyway, it shouldn't matter, since I expect Django to build an in-memory DB for every test. It's just an additional piece of info for the mistery.

What could be making Django check for the actual DB instead of doing all its thing in-memory?


Additional pieces of info:

I'll work to provide some code example (didn't provide it right from the start because there's a big test framework behind it, and I'll have to trim it). But for now, I'be adding infos and things that I get asked and things I suspect might be causing the issue:

  • I'm on Python 3.10.8 and Django 4.0.5

  • Most of the test cases are inheriting from django.test.TestCase, but some are inheriting from django.test.TransactionTestCase.

  • I'm using TransactionTestCase in some cases because I need to create dummy models (and maybe this implementation is part of the problem somehow). Example:

# THE DUMMY CLASS

@localized
class DummyClass(models.Model):
    """Represents a dummy model for testing purpose"""

    class Meta:
        app_label = 'apps.localizer'

    name = models.CharField(max_length=255, blank=True, null=True)
# THE TEST CASE SETUP/TEARDOWN

class LocalizedModelsTests(TransactionTestCase):
    def setUp(self):
        with connection.schema_editor() as schema_editor:
            schema_editor.create_model(DummyClass)

    def tearDown(self):
        with connection.schema_editor() as schema_editor:
            schema_editor.delete_model(DummyClass)
  • The database settings on settings.py (still on dev, so there's no other setting that could replace this one):
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

CodePudding user response:

Maybe you can use TestCase so the DB is created and use the setUp and tearDown to create the Dummy model like this:

class LocalizedModelsTests(TestCase):
    def setUp(self):
        # Create the dummy model
        with connection.schema_editor() as schema_editor:
            schema_editor.create_model(DummyClass)
        # Create any objects you need for your tests here
        DummyClass.objects.create(name='Test')

    def test_dummy_class(self):
        # Test the DummyClass model here

    def tearDown(self):
        DummyClass.objects.all().delete()
        # Delete the dummy model
        with connection.schema_editor() as schema_editor:
            schema_editor.delete_model(DummyClass)

Let me know if this help you.

CodePudding user response:

It seems like the issue is that your tests are modifying the actual database instead of creating an in-memory database for each test. This could be happening because you are using TransactionTestCase, which creates a new transaction for each test, allowing the tests to modify the database.

One solution would be to use TestCase instead of TransactionTestCase and use the db argument in your test methods to specify which database to use for that test. For example:

class LocalizedModelsTests(TestCase):
    def test_dummy_class(self, db='default'):
        # Code to test the DummyClass model using the specified database

This will create an in-memory database for each test, allowing the tests to modify the database without affecting the actual data.

Another solution would be to use the @override_settings decorator to specify the TEST database in your test methods. For example:

from django.test import override_settings

@override_settings(DATABASES={'default': {'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:'}})
class LocalizedModelsTests(TestCase):
    def test_dummy_class(self):
        # Code to test the DummyClass model using the in-memory database

This will override the default database settings and use an in-memory database for the duration of the test.

  • Related