skip to content

In Django tests, how do override_settings and modify_settings differ, and when do you reach for each?

level: middleimportance: must knowfreq 55%

answer

  1. replace a value vs edit a list
  2. append, prepend, remove
  3. method, class or with block
  4. setting_changed resets internal state

basics

~20 s

override_settings replaces whole setting values for a test or block; modify_settings edits list settings such as MIDDLEWARE or INSTALLED_APPS by appending, prepending or removing items. Both restore the originals when the test or block ends.

solid answer

~40 s

`django.test.override_settings(**kwargs)` swaps in new values for any settings — as a test-method decorator, as a decorator on a `SimpleTestCase` subclass, or as `with self.settings(...)` inside a test. `modify_settings` is for list settings when you only need a delta: `@modify_settings(MIDDLEWARE={'append': ..., 'prepend': ..., 'remove': [...]})`, each action taking a string or a list; `append` and `prepend` skip items already present and `remove` ignores items that are absent. Both restore the previous values on exit and send the `setting_changed` signal so Django can reset state built from settings, such as template engines or cache handlers. On one class, `modify_settings` is applied after `override_settings`. Never assign to `django.conf.settings` directly in a test: Django does not restore it.

code

python · 17 lines
python
from django.conf import settings
from django.test import TestCase, modify_settings, override_settings


@override_settings(LOGIN_URL='/accounts/sign-in/')
class SettingsOverrideTests(TestCase):
    def test_class_level_value(self):
        self.assertEqual(settings.LOGIN_URL, '/accounts/sign-in/')

    @modify_settings(MIDDLEWARE={'append': 'django.middleware.gzip.GZipMiddleware'})
    def test_middleware_appended(self):
        self.assertEqual(settings.MIDDLEWARE[-1], 'django.middleware.gzip.GZipMiddleware')

    def test_block_level_value(self):
        with self.settings(LOGIN_URL='/other/'):
            self.assertEqual(settings.LOGIN_URL, '/other/')
        self.assertEqual(settings.LOGIN_URL, '/accounts/sign-in/')

go deeper

for a junior

Know that tests must not edit settings directly and that override_settings or self.settings() changes a value temporarily and restores it afterwards.

for a middle

Explain the three application forms, why a method decorator is invisible to setUp, and exactly how modify_settings treats append, prepend and remove when items already exist or are missing.

for a senior

Diagnose overrides that silently do nothing: module-level aliases, startup-only settings, and your own caches that need a setting_changed receiver to stay coherent with the override.

for a principal

Weigh how much configuration variance a suite should carry; many per-test overrides can signal code reading settings too eagerly, and a team convention for setting_changed receivers keeps tests trustworthy.

## Two tools for one job Django tests often need a configuration that differs from the project's `settings.py`: another `LOGIN_URL`, an extra middleware, an app missing from `INSTALLED_APPS`. The `django.test` package ships two helpers for this, and both undo themselves when the test or block ends. - **`override_settings(**kwargs)`** replaces the *whole value* of each named setting. `@override_settings(LOGIN_URL='/other/login/')` makes `settings.LOGIN_URL` return `'/other/login/'` until the override exits. - **`modify_settings(**kwargs)`** edits *list-valued* settings such as `MIDDLEWARE` or `INSTALLED_APPS`. Instead of retyping the list, you describe a delta with the actions `append`, `prepend` and `remove`. The rule of thumb: use `override_settings` for scalars, dicts and whole-list replacement; use `modify_settings` when the test only cares that one or two entries are present or absent, so the test keeps working when someone adds a middleware to the project list next month. ## Three ways to apply them | Form | Where the override is active | Typical use | |---|---|---| | Method decorator `@override_settings(...)` | only while that test method runs | one test needs one value | | Class decorator on a `SimpleTestCase` subclass | from `setUpClass` on: `setUpTestData`, `setUp` and every test | a whole class shares the configuration | | Context manager `with self.settings(...)` or `with self.modify_settings(...)` | inside the `with` block | before/after comparisons inside one test | Two details trip people up. First, a method decorator wraps only the test method, and unittest calls `setUp()` *before* that method, so `setUp()` still sees the original value. Second, the class decorator accepts only a subclass of Django's `SimpleTestCase` (which includes `TestCase` and `TransactionTestCase`); decorating a plain `unittest.TestCase` raises `ValueError`. The class decorator modifies the class in place instead of returning a copy, and when both decorators sit on one class, `modify_settings` is applied after `override_settings`. ## How modify_settings reads its actions Each keyword is a setting name mapped to a dict of actions: 1. `append` adds items to the end of the list, skipping any that are already present. 2. `prepend` adds items to the front, again skipping items already present, and keeps the order you gave. 3. `remove` drops matching items; removing something that is not in the list does nothing. 4. Each action takes a single string or a list of strings; any other action name raises `ValueError`. The starting point is the setting's *current* value, so a method-level `modify_settings` stacked on a class-level one composes the two edits. ## What happens under the hood Neither helper touches your settings module. `override_settings` wraps the live settings object in a temporary holder that answers the overridden names and falls through to the original for everything else, swaps it in, and swaps the original back on exit. For every overridden name it sends the **`setting_changed`** signal (defined in `django.core.signals`, documented as `django.test.signals.setting_changed`) with `enter=True`, and again with `enter=False` on exit. Django's own receivers use it to discard state computed from settings, for example: - `TEMPLATES` resets the template engines; - `CACHES` closes and rebuilds the cache handlers; - `ROOT_URLCONF` clears the URL resolver caches; - `PASSWORD_HASHERS` clears the cached list of hashers; - `INSTALLED_APPS` is applied to the app registry before the new values take effect. ## Where an override does not reach The mechanism works only for code that reads `django.conf.settings` *at the moment it needs the value*. It misses these cases: - **Module-level aliases** such as `SENDER = settings.DEFAULT_FROM_EMAIL` are evaluated once, at import, so the override never reaches them. - **Your own caches** derived from a setting (an `lru_cache` function, a class attribute built at import) keep the old value unless you connect a `setting_changed` receiver that clears them. - **Settings consulted only at startup** change on the settings object but not inside Django's internals; the docs advise against overriding `DATABASES` at all. - **Direct assignment** such as `settings.DEBUG = True` inside a test is never restored and leaks into every later test in the process. To simulate a missing setting, apply `@override_settings()` with no arguments and `del settings.LOGIN_URL` inside the test; the deletion happens on the temporary holder and disappears with it. ## What interviewers listen for A strong answer names both helpers, shows the three application forms, explains the `append`/`prepend`/`remove` no-op rules, and gives at least one way an override fails to reach code — the module-level constant is the classic example.

  • Why does a method-level @override_settings not affect setUp()?
    The decorator wraps only the test method's callable, and unittest runs `setUp()` before calling it, so `setUp()` sees the original settings. Put the decorator on the class — Django enters it in `setUpClass`, before `setUpTestData` and `setUp` — or enter the override inside `setUp()` with `self.enterContext(override_settings(...))` so it is undone at cleanup.
  • How would you make your own cached, setting-derived value respect override_settings?
    Connect a receiver to the `setting_changed` signal from `django.core.signals`, check the `setting` keyword argument for the names your cache depends on, and clear the cache there, for example by calling `cache_clear()` on an `lru_cache` function. Django uses exactly this pattern internally for `PASSWORD_HASHERS`, `TEMPLATES` and `CACHES`.
  • How do you test code paths that run when a setting is not defined at all?
    Decorate the test with `@override_settings()` and no arguments, then `del settings.MY_SETTING` inside it. The deletion applies to the temporary settings holder, so when the override exits the original settings object — still containing the value — is restored.

saying these in an interview costs you the question

  • override_settings only works as a method decorator, never on classes or blocks
  • modify_settings append adds a duplicate if the middleware is already listed
  • Setting settings.DEBUG = True in a test is fine because Django restores it
  • A module-level constant copied from settings follows override_settings
  • override_settings can decorate any plain unittest.TestCase class