How to Unit test with different settings in Django?

Viewed 90802

Is there any simple mechanism for overriding Django settings for a unit test? I have a manager on one of my models that returns a specific number of the latest objects. The number of objects it returns is defined by a NUM_LATEST setting.

This has the potential to make my tests fail if someone were to change the setting. How can I override the settings on setUp() and subsequently restore them on tearDown()? If that isn't possible, is there some way I can monkey patch the method or mock the settings?

EDIT: Here is my manager code:

class LatestManager(models.Manager):
    """
    Returns a specific number of the most recent public Articles as defined by 
    the NEWS_LATEST_MAX setting.
    """
    def get_query_set(self):
        num_latest = getattr(settings, 'NEWS_NUM_LATEST', 10)
        return super(LatestManager, self).get_query_set().filter(is_public=True)[:num_latest]

The manager uses settings.NEWS_LATEST_MAX to slice the queryset. The getattr() is simply used to provide a default should the setting not exist.

14 Answers

You can do anything you like to the UnitTest subclass, including setting and reading instance properties:

from django.conf import settings

class MyTest(unittest.TestCase):
   def setUp(self):
       self.old_setting = settings.NUM_LATEST
       settings.NUM_LATEST = 5 # value tested against in the TestCase

   def tearDown(self):
       settings.NUM_LATEST = self.old_setting

Since the django test cases run single-threaded, however, I'm curious about what else may be modifying the NUM_LATEST value? If that "something else" is triggered by your test routine, then I'm not sure any amount of monkey patching will save the test without invalidating the veracity of the tests itself.

You can override setting even for a single test function.

from django.test import TestCase, override_settings

class SomeTestCase(TestCase):

    @override_settings(SOME_SETTING="some_value")
    def test_some_function():
        

or you can override setting for each function in class.

@override_settings(SOME_SETTING="some_value")
class SomeTestCase(TestCase):

    def test_some_function():
        

For pytest users.

The biggest issue is:

  • override_settings doesn't work with pytest.
  • Subclassing Django's TestCase will make it work but then you can't use pytest fixtures.

The solution is to use the settings fixture documented here.

Example

def test_with_specific_settings(settings):
    settings.DEBUG = False
    settings.MIDDLEWARE = []
    ..

And in case you need to update multiple fields

def override_settings(settings, kwargs):
    for k, v in kwargs.items():
        setattr(settings, k, v)


new_settings = dict(
    DEBUG=True,
    INSTALLED_APPS=[],
)


def test_with_specific_settings(settings):
    override_settings(settings, new_settings)

I created a new settings_test.py file which would import everything from settings.py file and modify whatever is different for testing purpose. In my case I wanted to use a different cloud storage bucket when testing. enter image description here

settings_test.py:

from project1.settings import *
import os

CLOUD_STORAGE_BUCKET = 'bucket_name_for_testing'

manage.py:

def main():

    # use seperate settings.py for tests
    if 'test' in sys.argv:
        print('using settings_test.py')
        os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project1.settings_test')
    else:
        os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project1.settings')

    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)

Found this while trying to fix some doctests... For completeness I want to mention that if you're going to modify the settings when using doctests, you should do it before importing anything else...

>>> from django.conf import settings

>>> settings.SOME_SETTING = 20

>>> # Your other imports
>>> from django.core.paginator import Paginator
>>> # etc

You can override settings in test in this way:

from django.test import TestCase, override_settings

test_settings = override_settings(
    DEFAULT_FILE_STORAGE='django.core.files.storage.FileSystemStorage',
    PASSWORD_HASHERS=(
        'django.contrib.auth.hashers.UnsaltedMD5PasswordHasher',
    )
)


@test_settings
class SomeTestCase(TestCase):
    """Your test cases in this class"""

And if you need these same settings in another file you can just directly import test_settings.

If you have multiple test files placed in a subdirectory (python package), you can override settings for all these files based on condition of presence of 'test' string in sys.argv

app
  tests
    __init__.py
    test_forms.py
    test_models.py

__init__.py:

import sys
from project import settings

if 'test' in sys.argv:
    NEW_SETTINGS = {
        'setting_name': value,
        'another_setting_name': another_value
    }
    settings.__dict__.update(NEW_SETTINGS)

Not the best approach. Used it to change Celery broker from Redis to Memory.

One setting for all tests in a testCase

class TestSomthing(TestCase):
    def setUp(self, **kwargs):
        with self.settings(SETTING_BAR={ALLOW_FOO=True})
            yield

override one setting in the testCase

from django.test import override_settings

    @override_settings(SETTING_BAR={ALLOW_FOO=False})
    def i_need_other_setting(self):
        ...

Important


Even though you are overriding these settings this will not apply to settings that your server initialize stuff with because it is already initialized, to do that you will need to start django with another setting module.

Related