Gabriel
Gabriel

Reputation: 71

Django executing tests for app not in INSTALLED_APPS

Under my Django project there are a few apps and all of them have unit tests. One of them that I'm working right now is supposed to be included only in dev/stage environments, so I'm enabling it using a environment variable.

When this variable is present it is added to INSTALLED_APPS and it is working just fine, the problem is that Django is executing the tests for this app even when it is not in INSTALLED_APPS, and it fails with the following message:

ImportError: Failed to import test module: debug.tests.unit.test_services`

...(traceback information)...

RuntimeError: Model class debug.models.Email doesn't declare an explicit app_label and isn't in an application in INSTALLED_APPS.

When I define the app_label in the class Meta of models in this app the error is different, it says it can't find a table, I assume that this is because the app is not in INSTALLED_APPS, so it's migrations are not executed.

OperationalError: no such table: debug_email

I'm not sure why Django executes the tests for all apps, but not it's migrations.

Am I missing something from Django configuration for tests?

Upvotes: 7

Views: 4834

Answers (3)

Dave Lawrence
Dave Lawrence

Reputation: 1236

https://docs.python.org/3/library/unittest.html#unittest.TestLoader.discover says:

If load_tests exists then discovery does not recurse into the package, load_tests is responsible for loading all tests in the package.

So in the lowest __init__.py in your app which you don't always want run:

from django.apps import apps

def load_tests(loader, tests, pattern):
    from django.conf import settings

    if apps.is_installed("your_dev_app"):
        # Actually load the tests - thanks to @barney-szabolcs
        return loader.discover(start_dir=dirname(abspath(__file__)), pattern=pattern)

Upvotes: 5

Barney Szabolcs
Barney Szabolcs

Reputation: 12514

You need to return the discovered tests in load_tests.

So, adding to @DaveLawrence's answer, the complete code is:

# your_dev_app/__init__.py

from django.apps import apps
from os.path import dirname, abspath


def load_tests(loader, tests, pattern):
    """
    loads tests for your_dev_app if it is installed.
    """
    from django.conf import settings
    if apps.is_installed("your_dev_app"):
        return loader.discover(start_dir=dirname(abspath(__file__)), pattern=pattern)

Upvotes: 5

cezar
cezar

Reputation: 12012

When you run:

python manage.py test

the command will look per default recursive for all files with the pattern test*.py in the working directory. It isn't affected by INSTALLED_APPS in settings.py.

You can specify a certain app to test it:

python manage.py test app_label

or specify a path:

python manage.py test myapp/tests

If you want to exclude some tests you can tag them and use the option --exclude-tag.

Run python manage.py test --help to get information on all options.

The official documentation gives a lot of information on the different possibilities how to run the tests.

EDIT:

If you have apps that are required only in the development environment, but not in the production, you could split your settings.py. One possible solution would be to outsource all development settings into a file local_settings.py and exclude it from versioning or from the production branch, i.e. don't push it in the production environment.

local_settings.py

DEBUG = True 
INSTALLED_APPS += (
    # Django Debug Toolbar would be for example
    # used only in development
    'debug_toolbar',
    'your dev app',
)

settings.py

try:
    from .local_settings import *
except ImportError:
    pass

Upvotes: 2

Related Questions