Chapter 22 · The test framework
22.8 Settings for one test: override_settings and TestContextDecorator
override_settings changes settings for a block, a test or a test case class and puts them back, and modify_settings adds items to a list setting or takes them out. Both are built on TestContextDecorator, which makes one pair of methods serve as a context manager, a function decorator and a class decorator; so are isolate_apps, override_script_prefix, override_system_checks and ignore_warnings.
A test may change something the whole process shares, for a few lines, one test or every test of a class, and the next test must find it as it was. What override_settings does to the settings on entering and leaving is told in Overriding settings.
TestContextDecorator: one change, three scopes
A subclass implements TestContextDecorator, which makes the change and may return a value, and TestContextDecorator, which undoes it. As a context manager it calls the first on entering and the second on leaving, letting an exception go on. Called on a class it uses decorate_class, on any other callable decorate_callable, and refuses anything else with a TypeError.
TestContextDecorator wraps the function in one that runs it inside with self, passing the value of enable under the keyword TestContextDecorator holds, if any. A coroutine function gets a coroutine function as its wrapper, so that, in the comment’s words, the with statement executes at the right time: a plain wrapper would leave the block on making the coroutine, before its body ran. SimpleTestCase runs a test method through async_to_sync only if it is a coroutine function, which the wrapper is (One test, in order: SimpleTestCase; TestContextDecorator, SimpleTestCase).
TestContextDecorator takes only a subclass of unittest, else TypeError, and replaces its setUp with one that calls enable, registers disable with unittest, sets the value of enable as the attribute TestContextDecorator names, if any, and calls the class’s own setUp. The change holds for each test from setUp to its cleanups, even when the class’s setUp raises, since unittest runs the cleanups of a test whose set-up failed. It does not hold in setUpClass, and a subclass that defines a setUp without calling its parent’s loses it.
override_settings on a test and on a class
On a function, override_settings uses the inherited decorate_callable. On a class, override_settings replaces no setUp: it takes only a subclass of SimpleTestCase, refusing any other with a ValueError, “Only subclasses of Django SimpleTestCase can be decorated with override_settings”, and records the values as SimpleTestCase. SimpleTestCase enters an override_settings of those values with unittest, which leaves it as a class cleanup, after tearDownClass. The values hold for a TestCase’s setUpTestData, every test and tearDownClass, but not for what a class’s own setUpClass does before calling its parent’s.
A subclass inherits _overridden_settings. If the class has one already, override_settings makes a new dictionary of those values and its own, which win, the comment saying this prevents subclasses from altering their parent. Of two decorators on one class, the outer wins.
A test method’s override is entered when the method is called, after setUp, inside the class’s. Here the class mounts the site under archive/, and its tests put the project’s URLconf back:
# harbour/office/tests/test_overrides.py
from django.apps import apps
from django.conf import settings
from django.db import models
from django.test import SimpleTestCase, modify_settings, override_settings
from django.test.utils import isolate_apps
from django.urls import include, path, reverse
urlpatterns = [path("archive/", include("harbour.urls"))]
@override_settings(ROOT_URLCONF="harbour.office.tests.test_overrides")
class ArchiveUrlTests(SimpleTestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
print("setUpClass:", reverse("ship-detail", args=[1]))
def setUp(self):
print("setUp:", reverse("ship-detail", args=[1]))
@override_settings(ROOT_URLCONF="harbour.urls")
def test_method_override(self):
print("test:", reverse("ship-detail", args=[1]))
@override_settings(ROOT_URLCONF="harbour.urls")
async def test_async(self):
print("test:", reverse("ship-detail", args=[1]))
class MiddlewareTests(SimpleTestCase):
@modify_settings(MIDDLEWARE={"remove": "django.middleware.csrf.CsrfViewMiddleware"})
def test_without_csrf(self):
print(*settings.MIDDLEWARE, sep="\n")
class BuoyTests(SimpleTestCase):
@isolate_apps("harbour.office", kwarg_name="registry")
def test_buoy(self, registry):
class Buoy(models.Model):
number = models.PositiveIntegerField()
print([m.__name__ for m in registry.get_models()], Buoy in apps.get_models())
$ python manage.py test harbour.office.tests.test_overrides -v 2
Found 4 test(s).
Skipping setup of unused database(s): archive, default.
System check identified no issues (0 silenced).
setUpClass: /archive/ships/1/
test_async (harbour.office.tests.test_overrides.ArchiveUrlTests.test_async) ... setUp: /archive/ships/1/
test: /ships/1/
ok
test_method_override (harbour.office.tests.test_overrides.ArchiveUrlTests.test_method_override) ... setUp: /archive/ships/1/
test: /ships/1/
ok
test_buoy (harbour.office.tests.test_overrides.BuoyTests.test_buoy) ... ['Buoy'] False
ok
test_without_csrf (harbour.office.tests.test_overrides.MiddlewareTests.test_without_csrf) ... django.contrib.sessions.middleware.SessionMiddleware
django.middleware.common.CommonMiddleware
django.contrib.auth.middleware.AuthenticationMiddleware
django.contrib.messages.middleware.MessageMiddleware
ok
----------------------------------------------------------------------
Ran 4 tests in 0.005s
OK
setUpClass and each setUp saw the class’s URLconf, and each method its own, the async one too.
If a receiver raises while an override is entered, override_settings keeps the exception as override_settings and calls disable, which restores the settings, sends the signal for leaving, and raises it. COMPLEX_OVERRIDE_SETTINGS, the settings that may not work well with override_settings (ticket #19031), holds "DATABASES" alone, and for it a receiver warns on entering “Overriding setting DATABASES can lead to unexpected behavior.”. SimpleTestCase and SimpleTestCase return a new decorator of each kind, for a with block.
modify_settings: items of a list
modify_settings takes, for each setting, a dictionary of actions, "append", "prepend" and "remove", each with a list of items or a string, which counts as one item. modify_settings works out each new list for override_settings, from a copy of the setting’s value (an empty list if it is not defined), applying the actions in the dictionary’s order, adding only items not already there and removing every item named. Any other action, "insert" say, raises ValueError (“Unsupported action: insert”) before the settings are touched.
On a class it uses override_settings, with its refusal and message. modify_settings keeps a list of pairs, a setting and its actions, as SimpleTestCase, a subclass’s added to a copy of its parent’s, and SimpleTestCase enters it after the class’s override_settings, whichever decorator is written first. Where the list names a setting twice, from a parent and a subclass, enable cumulates, in its comment’s word: the later entry starts from the list the earlier made.
The other changes
isolate_apps gives a test its own app registry: isolate_apps keeps Options, puts in its place a new Apps populated with the applications it is given, and returns it. A new model’s options take that registry as their apps unless its Meta gives one, and ModelBase registers a model that is not abstract there, not in the project’s registry. The run’s Buoy was in the new registry alone. On a class it is entered in setUp, so a model in the class’s body is not isolated.
override_script_prefix sets a script prefix and puts the old one back (The script prefix, the current URLconf and the caches; override_script_prefix). override_system_checks leaves the check registry only the checks it is given, and the deployment checks too when its second argument is not None (The system checks; override_system_checks). ignore_warnings adds an "ignore" filter inside warnings, which leaving restores.