151

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.

Soviut
  • 88,194
  • 49
  • 192
  • 260
  • @Anto -- can you explain why or provide a better answer? – user Jan 13 '15 at 14:37
  • It changed in the meantime; the former accepted one was [this one](http://stackoverflow.com/a/913596/1030960) ;) – Anto Jan 14 '15 at 01:28

14 Answers14

196

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.

djvg
  • 11,722
  • 5
  • 72
  • 103
slinkp
  • 3,406
  • 2
  • 21
  • 18
  • 4
    I'd say this is the best way of doing this now in Django 1.4+ – Michael Mior Jun 28 '13 at 12:59
  • 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. – mlissner Oct 22 '14 at 17:56
  • 2
    Newer versions of Django have a specific context manager for this: https://docs.djangoproject.com/en/1.8/topics/testing/tools/#overriding-settings – Akhorus Nov 06 '15 at 14:26
  • 1
    My favorite: `@modify_settings(MIDDLEWARE_CLASSES=...` (thank you for this answer) – guettli Oct 14 '16 at 09:28
53

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.

Jarret Hardie
  • 95,172
  • 10
  • 132
  • 126
  • 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. – Soviut May 27 '09 at 03:48
  • 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. – Ciantic Jul 17 '10 at 19:41
  • 1
    this is good example for version Django older then 1.4. For >= 1.4 answer http://stackoverflow.com/a/6415129/190127 more correct – Oduvan Jan 09 '13 at 14:38
  • Use https://docs.djangoproject.com/en/dev/topics/testing/tools/#overriding-settings 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. – fuzzy-waffle May 29 '14 at 17:34
  • "Since the django test cases run single-threaded": which is no longer the case in Django 1.9. – Wtower Dec 03 '15 at 10:29
34

You can pass --settings option when running tests

python manage.py test --settings=mysite.settings_local
MicroPyramid
  • 1,570
  • 19
  • 22
26

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

hspandher
  • 15,934
  • 2
  • 32
  • 45
  • 2
    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 Jun 12 '18 at 03:50
  • For me, this also caused testing settings to be used when running the server with `runserver`. I had to use `sys.argv[1:2] == ["test"]` instead of `'test' in sys.argv` in the condition to make this work as required ([reference](https://adamj.eu/tech/2020/05/14/how-to-check-the-running-django-command/#warning-incorrect-sys-argv-checks)). – Clerni Jul 05 '23 at 17:43
20

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()
Community
  • 1
  • 1
akaihola
  • 26,309
  • 7
  • 59
  • 69
  • This is really cool solution. For some reason my settings were not working properly in the unit tests. Very elegant solution, thanks for sharing. – Tomas Mar 22 '12 at 21:26
  • 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 May 29 '12 at 17:36
  • I'll edit the answer for posterity. I hope I'm doing this right! :) – Dustin Rasener May 29 '12 at 17:37
17

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():
        
shivansh
  • 450
  • 4
  • 8
13

@override_settings is great if you don't have many differences between your production and testing environment configurations.

In other case you'd better just have different settings files. In this case your project will look like this:

your_project
    your_app
        ...
    settings
        __init__.py
        base.py
        dev.py
        test.py
        production.py
    manage.py

So you need to have your most of your settings in base.py and then in other files you need to import all everything from there, and override some options. Here's what your test.py file will look like:

from .base import *

DEBUG = False

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': 'app_db_test'
    }
}

PASSWORD_HASHERS = (
    'django.contrib.auth.hashers.MD5PasswordHasher',
)

LOGGING = {}

And then you either need to specify --settings option as in @MicroPyramid answer, or specify DJANGO_SETTINGS_MODULE environment variable and then you can run your tests:

export DJANGO_SETTINGS_MODULE=settings.test
python manage.py test 
Dmitrii Mikhailov
  • 5,053
  • 7
  • 43
  • 69
  • 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 https://github.com/andela/ah-backend-iroquois/tree/develop/authors , like how will i handle that logic ? – Lutaaya Huzaifah Idris Aug 30 '18 at 18:26
  • Because i use *nosetests* to run tests , now how will this be run?, in the testing environment not in **development environment** – Lutaaya Huzaifah Idris Aug 30 '18 at 18:28
12

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)
Pithikos
  • 18,827
  • 15
  • 113
  • 136
  • unless I've done something wrong, I can use the `override_settings` decorator with pytest; my test classes do not subclass `TestCase` – A G Aug 08 '21 at 15:30
7

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)
Aseem
  • 5,848
  • 7
  • 45
  • 69
  • This may work. But if we try to import some constant from settings file in our code - `from django.conf import settings` we will still get value from old `settings.py` file. we wont get new value from `settings_test.py` file – Aseem Jan 11 '23 at 07:15
  • For me, this also caused testing settings to be used when running the server with `runserver`. I had to use `sys.argv[1:2] == ["test"]` instead of `'test' in sys.argv` in the condition to make this work as required ([reference](https://adamj.eu/tech/2020/05/14/how-to-check-the-running-django-command/#warning-incorrect-sys-argv-checks)). – Clerni Jul 05 '23 at 17:32
  • good job, bro. Manage.py tests works as intended. Keep it up. – Koops Jul 25 '23 at 17:24
3

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
Jiaaro
  • 74,485
  • 42
  • 169
  • 190
2

I'm using pytest.

I managed to solve this the following way:

import django    
import app.setting
import modules.that.use.setting

# do some stuff with default setting
setting.VALUE = "some value"
django.setup()
import importlib
importlib.reload(app.settings)
importlib.reload(modules.that.use.setting)
# do some stuff with settings new value
Brontes
  • 134
  • 5
  • 1
    An easier way for pytest users is to use the `settings` fixture. See [this answer](https://stackoverflow.com/a/58755626/456550) from Pithikos for an example. – Christian Long Jul 26 '22 at 14:57
2

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.

omushpapa
  • 1,663
  • 20
  • 25
0

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.

Ledorub
  • 354
  • 3
  • 9
0

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.

ניר
  • 1,204
  • 1
  • 8
  • 28