Django tests don't use test database if alias is not specified in testcases.

Hi everyone! I’ve faced a kind of bug when using django tests and want to share all the information to help to fix or overpass it.

In my case, TestRunner must use only test database, but when I run tests for specific module, testcase made changes in the original database:

python manage.py test --keepdb apps.some_app

But when I run all tests, it uses the test database:

python manage.py test --keepdb

After hours of debugging I found that TestRunner uses the original database, if it’s alias is not specified in testcases that I run. But when I run all tests, in some testcases I have parameter “databases=[‘default’, ‘replica’]”, and everything works fine.

Is it kind of bug, or ‘undeclared feature’?

See all additional information below:

settings.py

DATABASES = {
    'default': env.db('DATABASE_URL', default=''),
    'replica': env.db('DATABASE_REPLICA_URL', default=''),
...
}
DATABASES['replica']['ENGINE'] = 'apps.core.db_backends.open'
DATABASES['default']['ENGINE'] = 'apps.core.db_backends.open'

for db_name in DATABASES:
    DATABASES[db_name]['TEST'] = {
        'SERIALIZE': False,
        'DEPENDENCIES': [],
    }

test case without database declaration

class TestService(TestCase):
    @classmethod
    def setUpClass(cls) -> None:
        cls._service_class = Service

        # NOT OKAY: IT REMOVES DATA IN ORIGINAL DATABASE!
        ObjectType.objects.all().delete()
        ObjectTypeFactory.create_batch(5)
    
    def test_service(self):
        ...

test with declared database attribute

class TesStatistic(TestCase):
    databases = ['replica', 'default']

    @classmethod
    def setUpClass(cls) -> None:

        # OKAY, IT MAKES CHANGES IN TEST DATABASE
        cls.incidents_list: List= StatisticFactory.create_batch(5)
        cls.incidents_to_delete: List = StatisticFactory.create_batch(5)

the way to overpass this bug
Create your own TestRunner and re-define setup_databases method like this:

class KeepDBTestRunner(DiscoverRunner):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.keepdb = True

    def setup_databases(self, **kwargs):
        
        # define aliases here. In original class aliases will be 'None' (from calling 
        # self.get_databases(suite)) and it causes a problem.

        aliases = {'default', 'replica'}  
        return super().setup_databases(aliases=aliases)