How to Unit test with different settings in Django?

85,897

Solution 1

EDIT: This answer applies if you want to change settings for a small number of specific tests.

Since Django 1.4, there are ways to override settings during tests: https://docs.djangoproject.com/en/stable/topics/testing/tools/#overriding-settings

TestCase will have a self.settings context manager, and there will also be an @override_settings decorator that can be applied to either a test method or a whole TestCase subclass.

These features did not exist yet in Django 1.3.

If you want to change settings for all your tests, you'll want to create a separate settings file for test, which can load and override settings from your main settings file. There are several good approaches to this in the other answers; I have seen successful variations on both hspander's and dmitrii's approaches.

Solution 2

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.

Solution 3

You can pass --settings option when running tests

python manage.py test --settings=mysite.settings_local

Solution 4

Although overriding settings configuration on runtime might help, in my opinion you should create a separate file for testing. This saves lot of configuration for testing and this would ensure that you never end up doing something irreversible (like cleaning staging database).

Say your testing file exists in 'my_project/test_settings.py', add

settings = 'my_project.test_settings' if 'test' in sys.argv else 'my_project.settings'

in your manage.py. This will ensure that when you run python manage.py test you use test_settings only. If you are using some other testing client like pytest, you could as easily add this to pytest.ini

Solution 5

Update: the solution below is only needed on Django 1.3.x and earlier. For >1.4 see slinkp's answer.

If you change settings frequently in your tests and use Python ≥2.5, this is also handy:

from contextlib import contextmanager

class SettingDoesNotExist:
    pass

@contextmanager
def patch_settings(**kwargs):
    from django.conf import settings
    old_settings = []
    for key, new_value in kwargs.items():
        old_value = getattr(settings, key, SettingDoesNotExist)
        old_settings.append((key, old_value))
        setattr(settings, key, new_value)
    yield
    for key, old_value in old_settings:
        if old_value is SettingDoesNotExist:
            delattr(settings, key)
        else:
            setattr(settings, key, old_value)

Then you can do:

with patch_settings(MY_SETTING='my value', OTHER_SETTING='other value'):
    do_my_tests()
Share:
85,897
Sami Ahmed Siddiqui
Author by

Sami Ahmed Siddiqui

Updated on July 08, 2022

Comments

  • Sami Ahmed Siddiqui
    Sami Ahmed Siddiqui almost 2 years

    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.

    • user
      user over 9 years
      @Anto -- can you explain why or provide a better answer?
    • Anto
      Anto over 9 years
      It changed in the meantime; the former accepted one was this one ;)
  • Sami Ahmed Siddiqui
    Sami Ahmed Siddiqui almost 15 years
    Your example worked. This has been an eye-opener in terms of the scope of unit testing and how the settings in the tests file propagate down through the call stack.
  • Ciantic
    Ciantic almost 14 years
    This does not work with settings.TEMPLATE_LOADERS... So this is not general way at least, the settings or Django is not reloaded or anything with this trick.
  • Tomas
    Tomas about 12 years
    This is really cool solution. For some reason my settings were not working properly in the unit tests. Very elegant solution, thanks for sharing.
  • Dustin Rasener
    Dustin Rasener almost 12 years
    I am using this code, but I had problems with cascading test failures, because settings would not get reverted if the test in question failed. To address this, I added a try/finally around the yield statement, with the final part of the function contained in the finally block, so that settings are always reverted.
  • Dustin Rasener
    Dustin Rasener almost 12 years
    I'll edit the answer for posterity. I hope I'm doing this right! :)
  • Oduvan
    Oduvan over 11 years
    this is good example for version Django older then 1.4. For >= 1.4 answer stackoverflow.com/a/6415129/190127 more correct
  • Michael Mior
    Michael Mior almost 11 years
    I'd say this is the best way of doing this now in Django 1.4+
  • fuzzy-waffle
    fuzzy-waffle almost 10 years
    Use docs.djangoproject.com/en/dev/topics/testing/tools/… Patching with setUp and tearDown like this is a great way to make really fragile tests that are more verbose than they need to be. If you need to patch something like this use something like flexmock.
  • mlissner
    mlissner over 9 years
    How do you later access that setting from within the tests? Best I've found is something like self.settings().wrapped.MEDIA_ROOT, but that's pretty terrible.
  • Akhorus
    Akhorus over 8 years
    Newer versions of Django have a specific context manager for this: docs.djangoproject.com/en/1.8/topics/testing/tools/…
  • Wtower
    Wtower over 8 years
    "Since the django test cases run single-threaded": which is no longer the case in Django 1.9.
  • guettli
    guettli over 7 years
    My favorite: @modify_settings(MIDDLEWARE_CLASSES=... (thank you for this answer)
  • holms
    holms about 6 years
    it stopped to find apps which are located in settings.dev which is extension of settings.base
  • ramwin
    ramwin almost 6 years
    I think this is a good solution for me. I have many too many tests and code which is using cache. It will be difficult for me to override the settings one by one. I will create two config file and determine which one to use. The MicroPyramid's answer is also avaiable, but it will be dangerous if I forgot to add the settings parameters once.
  • ramwin
    ramwin almost 6 years
    I think it will be dangerous if someone forget to add the settings parameters once.
  • Lutaaya Huzaifah Idris
    Lutaaya Huzaifah Idris over 5 years
    Hello . Dmitrii, thanks for your answer am having the same case with this answer, but i would like to get more guidance on how the app will know, the environment we are in (testing or production), have a look on my branch, check out my repo github.com/andela/ah-backend-iroquois/tree/develop/authors , like how will i handle that logic ?
  • Lutaaya Huzaifah Idris
    Lutaaya Huzaifah Idris over 5 years
    Because i use nosetests to run tests , now how will this be run?, in the testing environment not in development environment
  • A G
    A G almost 3 years
    unless I've done something wrong, I can use the override_settings decorator with pytest; my test classes do not subclass TestCase