Loading django/test/__init__.py +2 −2 Original line number Diff line number Diff line Loading @@ -8,10 +8,10 @@ from django.test.testcases import ( SimpleTestCase, LiveServerTestCase, skipIfDBFeature, skipUnlessDBFeature ) from django.test.utils import override_settings from django.test.utils import modify_settings, override_settings __all__ = [ 'Client', 'RequestFactory', 'TestCase', 'TransactionTestCase', 'SimpleTestCase', 'LiveServerTestCase', 'skipIfDBFeature', 'skipUnlessDBFeature', 'override_settings', 'skipUnlessDBFeature', 'modify_settings', 'override_settings', ] django/test/testcases.py +20 −7 Original line number Diff line number Diff line Loading @@ -32,7 +32,7 @@ from django.test.client import Client from django.test.html import HTMLParseError, parse_html from django.test.signals import setting_changed, template_rendered from django.test.utils import (CaptureQueriesContext, ContextList, override_settings, compare_xml) override_settings, modify_settings, compare_xml) from django.utils.encoding import force_text from django.utils import six from django.utils.six.moves.urllib.parse import urlsplit, urlunsplit, urlparse, unquote Loading Loading @@ -164,7 +164,8 @@ class SimpleTestCase(unittest.TestCase): # The class we'll use for the test client self.client. # Can be overridden in derived classes. client_class = Client _custom_settings = None _overridden_settings = None _modified_settings = None def __call__(self, result=None): """ Loading Loading @@ -197,9 +198,12 @@ class SimpleTestCase(unittest.TestCase): * If the class has a 'urls' attribute, replace ROOT_URLCONF with it. * Clearing the mail test outbox. """ if self._custom_settings: self._overridden = override_settings(**self._custom_settings) self._overridden.enable() if self._overridden_settings: self._overridden_context = override_settings(**self._overridden_settings) self._overridden_context.enable() if self._modified_settings: self._modified_context = modify_settings(self._modified_settings) self._modified_context.enable() self.client = self.client_class() self._urlconf_setup() mail.outbox = [] Loading @@ -217,8 +221,10 @@ class SimpleTestCase(unittest.TestCase): * Putting back the original ROOT_URLCONF if it was changed. """ self._urlconf_teardown() if self._custom_settings: self._overridden.disable() if self._modified_settings: self._modified_context.disable() if self._overridden_settings: self._overridden_context.disable() def _urlconf_teardown(self): set_urlconf(None) Loading @@ -233,6 +239,13 @@ class SimpleTestCase(unittest.TestCase): """ return override_settings(**kwargs) def modify_settings(self, **kwargs): """ A context manager that temporarily applies changes a list setting and reverts back to the original value when exiting the context. """ return modify_settings(**kwargs) def assertRedirects(self, response, expected_url, status_code=302, target_status_code=200, host=None, msg_prefix='', fetch_redirect_response=True): Loading django/test/utils.py +63 −12 Original line number Diff line number Diff line Loading @@ -24,8 +24,10 @@ from django.utils.translation import deactivate __all__ = ( 'Approximate', 'ContextList', 'get_runner', 'override_settings', 'requires_tz_support', 'setup_test_environment', 'teardown_test_environment', 'Approximate', 'ContextList', 'get_runner', 'modify_settings', 'override_settings', 'requires_tz_support', 'setup_test_environment', 'teardown_test_environment', ) RESTORE_LOADERS_ATTR = '_original_template_source_loaders' Loading Loading @@ -191,8 +193,6 @@ class override_settings(object): """ def __init__(self, **kwargs): self.options = kwargs # Special case that requires updating the app cache, a core feature. self.installed_apps = self.options.get('INSTALLED_APPS') def __enter__(self): self.enable() Loading @@ -207,11 +207,7 @@ class override_settings(object): raise Exception( "Only subclasses of Django SimpleTestCase can be decorated " "with override_settings") if test_func._custom_settings: test_func._custom_settings = dict( test_func._custom_settings, **self.options) else: test_func._custom_settings = self.options self.save_options(test_func) return test_func else: @wraps(test_func) Loading @@ -220,14 +216,22 @@ class override_settings(object): return test_func(*args, **kwargs) return inner def save_options(self, test_func): if test_func._overridden_settings is None: test_func._overridden_settings = self.options else: # Duplicate dict to prevent subclasses from altering their parent. test_func._overridden_settings = dict( test_func._overridden_settings, **self.options) def enable(self): override = UserSettingsHolder(settings._wrapped) for key, new_value in self.options.items(): setattr(override, key, new_value) self.wrapped = settings._wrapped settings._wrapped = override if self.installed_apps is not None: app_cache.set_installed_apps(self.installed_apps) if 'INSTALLED_APPS' in self.options: app_cache.set_installed_apps(settings.INSTALLED_APPS) for key, new_value in self.options.items(): setting_changed.send(sender=settings._wrapped.__class__, setting=key, value=new_value, enter=True) Loading @@ -235,7 +239,7 @@ class override_settings(object): def disable(self): settings._wrapped = self.wrapped del self.wrapped if self.installed_apps is not None: if 'INSTALLED_APPS' in self.options: app_cache.unset_installed_apps() for key in self.options: new_value = getattr(settings, key, None) Loading @@ -243,6 +247,53 @@ class override_settings(object): setting=key, value=new_value, enter=False) class modify_settings(override_settings): """ Like override_settings, but makes it possible to append, prepend or remove items instead of redefining the entire list. """ def __init__(self, *args, **kwargs): if args: # Hack used when instaciating from SimpleTestCase._pre_setup. assert not kwargs self.operations = args[0] else: assert not args self.operations = list(kwargs.items()) def save_options(self, test_func): if test_func._modified_settings is None: test_func._modified_settings = self.operations else: # Duplicate list to prevent subclasses from altering their parent. test_func._modified_settings = list( test_func._modified_settings) + self.operations def enable(self): self.options = {} for name, operations in self.operations: try: # When called from SimpleTestCase._pre_setup, values may be # overridden several times; cumulate changes. value = self.options[name] except KeyError: value = list(getattr(settings, name, [])) for action, items in operations.items(): # items my be a single value or an iterable. if isinstance(items, six.string_types): items = [items] if action == 'append': value = value + [item for item in items if item not in value] elif action == 'prepend': value = [item for item in items if item not in value] + value elif action == 'remove': value = [item for item in value if item not in items] else: raise ValueError("Unsupported action: %s" % action) self.options[name] = value super(modify_settings, self).enable() def compare_xml(want, got): """Tries to do a 'xml-comparison' of want and got. Plain string comparison doesn't always work because, for example, attribute Loading docs/topics/testing/overview.txt +86 −24 Original line number Diff line number Diff line Loading @@ -1335,7 +1335,7 @@ Overriding settings For testing purposes it's often useful to change a setting temporarily and revert to the original value after running the testing code. For this use case Django provides a standard Python context manager (see :pep:`343`) Django provides a standard Python context manager (see :pep:`343`) called :meth:`~django.test.SimpleTestCase.settings`, which can be used like this:: from django.test import TestCase Loading @@ -1356,12 +1356,41 @@ Django provides a standard Python context manager (see :pep:`343`) This example will override the :setting:`LOGIN_URL` setting for the code in the ``with`` block and reset its value to the previous state afterwards. .. method:: SimpleTestCase.modify_settings .. versionadded:: 1.7 It can prove unwieldy to redefine settings that contain a list of values. In practice, adding or removing values is often sufficient. The :meth:`~django.test.SimpleTestCase.modify_settings` context manager makes it easy:: from django.test import TestCase class MiddlewareTestCase(TestCase): def test_cache_middleware(self): with self.modify_settings(MIDDLEWARE_CLASSES={ 'append': 'django.middleware.cache.FetchFromCacheMiddleware', 'prepend': 'django.middleware.cache.UpdateCacheMiddleware', 'remove': [ 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', ], }): response = self.client.get('/') # ... For each action, you can supply either a list of values or a string. When the value already exists in the list, ``append`` and ``prepend`` have no effect; neither does ``remove`` when the value doesn't exist. .. function:: override_settings In case you want to override a setting for just one test method or even the whole :class:`~django.test.TestCase` class, Django provides the :func:`~django.test.override_settings` decorator (see :pep:`318`). It's used like this:: In case you want to override a setting for a test method, Django provides the :func:`~django.test.override_settings` decorator (see :pep:`318`). It's used like this:: from django.test import TestCase, override_settings Loading @@ -1372,7 +1401,7 @@ used like this:: response = self.client.get('/sekrit/') self.assertRedirects(response, '/other/login/?next=/sekrit/') The decorator can also be applied to test case classes:: The decorator can also be applied to :class:`~django.test.TestCase` classes:: from django.test import TestCase, override_settings Loading @@ -1385,17 +1414,50 @@ The decorator can also be applied to test case classes:: .. versionchanged:: 1.7 Previously, ``override_settings`` was imported from ``django.test.utils``. Previously, ``override_settings`` was imported from ``django.test.utils``. .. function:: modify_settings .. versionadded:: 1.7 Likewise, Django provides the :func:`~django.test.modify_settings` decorator:: from django.test import TestCase, modify_settings class MiddlewareTestCase(TestCase): @modify_settings(MIDDLEWARE_CLASSES={ 'append': 'django.middleware.cache.FetchFromCacheMiddleware', 'prepend': 'django.middleware.cache.UpdateCacheMiddleware', }) def test_cache_middleware(self): response = self.client.get('/') # ... The decorator can also be applied to test case classes:: from django.test import TestCase, modify_settings @modify_settings(MIDDLEWARE_CLASSES={ 'append': 'django.middleware.cache.FetchFromCacheMiddleware', 'prepend': 'django.middleware.cache.UpdateCacheMiddleware', }) class MiddlewareTestCase(TestCase): def test_cache_middleware(self): response = self.client.get('/') # ... .. note:: When given a class, the decorator modifies the class directly and returns it; it doesn't create and return a modified copy of it. So if you try to tweak the above example to assign the return value to a different name than ``LoginTestCase``, you may be surprised to find that the original ``LoginTestCase`` is still equally affected by the decorator. When given a class, these decorators modify the class directly and return it; they don't create and return a modified copy of it. So if you try to tweak the above examples to assign the return value to a different name than ``LoginTestCase`` or ``MiddlewareTestCase``, you may be surprised to find that the original test case classes are still equally affected by the decorator. For a given class, :func:`~django.test.modify_settings` is always applied after :func:`~django.test.override_settings`. .. warning:: Loading @@ -1403,17 +1465,17 @@ The decorator can also be applied to test case classes:: initialization of Django internals. If you change them with ``override_settings``, the setting is changed if you access it via the ``django.conf.settings`` module, however, Django's internals access it differently. Effectively, using ``override_settings`` with these settings is probably not going to do what you expect it to do. differently. Effectively, using :func:`~django.test.override_settings` or :func:`~django.test.modify_settings` with these settings is probably not going to do what you expect it to do. We do not recommend using ``override_settings`` with :setting:`DATABASES`. Using ``override_settings`` with :setting:`CACHES` is possible, but a bit tricky if you are using internals that make using of caching, like We do not recommend altering the :setting:`DATABASES` setting. Altering the :setting:`CACHES` setting is possible, but a bit tricky if you are using internals that make using of caching, like :mod:`django.contrib.sessions`. For example, you will have to reinitialize the session backend in a test that uses cached sessions and overrides :setting:`CACHES`. You can also simulate the absence of a setting by deleting it after settings have been overridden, like this:: Loading @@ -1423,10 +1485,10 @@ have been overridden, like this:: ... When overriding settings, make sure to handle the cases in which your app's code uses a cache or similar feature that retains state even if the setting is changed. Django provides the :data:`django.test.signals.setting_changed` signal that lets you register callbacks to clean up and otherwise reset state when settings are changed. code uses a cache or similar feature that retains state even if the setting is changed. Django provides the :data:`django.test.signals.setting_changed` signal that lets you register callbacks to clean up and otherwise reset state when settings are changed. Django itself uses this signal to reset various data: Loading tests/settings_tests/tests.py +57 −4 Original line number Diff line number Diff line Loading @@ -6,19 +6,57 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.http import HttpRequest from django.test import SimpleTestCase, TransactionTestCase, TestCase, signals from django.test.utils import override_settings from django.test.utils import modify_settings, override_settings from django.utils import six @override_settings(TEST='override', TEST_OUTER='outer') @modify_settings(ITEMS={ 'prepend': ['b'], 'append': ['d'], 'remove': ['a', 'e'] }) @override_settings(ITEMS=['a', 'c', 'e'], ITEMS_OUTER=[1, 2, 3], TEST='override', TEST_OUTER='outer') class FullyDecoratedTranTestCase(TransactionTestCase): available_apps = [] def test_override(self): self.assertListEqual(settings.ITEMS, ['b', 'c', 'd']) self.assertListEqual(settings.ITEMS_OUTER, [1, 2, 3]) self.assertEqual(settings.TEST, 'override') self.assertEqual(settings.TEST_OUTER, 'outer') @modify_settings(ITEMS={ 'append': ['e', 'f'], 'prepend': ['a'], 'remove': ['d', 'c'], }) def test_method_list_override(self): self.assertListEqual(settings.ITEMS, ['a', 'b', 'e', 'f']) self.assertListEqual(settings.ITEMS_OUTER, [1, 2, 3]) @modify_settings(ITEMS={ 'append': ['b'], 'prepend': ['d'], 'remove': ['a', 'c', 'e'], }) def test_method_list_override_no_ops(self): self.assertListEqual(settings.ITEMS, ['b', 'd']) @modify_settings(ITEMS={ 'append': 'e', 'prepend': 'a', 'remove': 'c', }) def test_method_list_override_strings(self): self.assertListEqual(settings.ITEMS, ['a', 'b', 'd', 'e']) @modify_settings(ITEMS={'remove': ['b', 'd']}) @modify_settings(ITEMS={'append': ['b'], 'prepend': ['d']}) def test_method_list_override_nested_order(self): self.assertListEqual(settings.ITEMS, ['d', 'c', 'b']) @override_settings(TEST='override2') def test_method_override(self): self.assertEqual(settings.TEST, 'override2') Loading @@ -31,14 +69,26 @@ class FullyDecoratedTranTestCase(TransactionTestCase): self.assertEqual(FullyDecoratedTranTestCase.__module__, __name__) @override_settings(TEST='override') @modify_settings(ITEMS={ 'prepend': ['b'], 'append': ['d'], 'remove': ['a', 'e'] }) @override_settings(ITEMS=['a', 'c', 'e'], TEST='override') class FullyDecoratedTestCase(TestCase): def test_override(self): self.assertListEqual(settings.ITEMS, ['b', 'c', 'd']) self.assertEqual(settings.TEST, 'override') @modify_settings(ITEMS={ 'append': 'e', 'prepend': 'a', 'remove': 'c', }) @override_settings(TEST='override2') def test_method_override(self): self.assertListEqual(settings.ITEMS, ['a', 'b', 'd', 'e']) self.assertEqual(settings.TEST, 'override2') Loading Loading @@ -73,14 +123,17 @@ class ClassDecoratedTestCase(ClassDecoratedTestCaseSuper): self.fail() @override_settings(TEST='override-parent') @modify_settings(ITEMS={'append': 'mother'}) @override_settings(ITEMS=['father'], TEST='override-parent') class ParentDecoratedTestCase(TestCase): pass @modify_settings(ITEMS={'append': ['child']}) @override_settings(TEST='override-child') class ChildDecoratedTestCase(ParentDecoratedTestCase): def test_override_settings_inheritance(self): self.assertEqual(settings.ITEMS, ['father', 'mother', 'child']) self.assertEqual(settings.TEST, 'override-child') Loading Loading
django/test/__init__.py +2 −2 Original line number Diff line number Diff line Loading @@ -8,10 +8,10 @@ from django.test.testcases import ( SimpleTestCase, LiveServerTestCase, skipIfDBFeature, skipUnlessDBFeature ) from django.test.utils import override_settings from django.test.utils import modify_settings, override_settings __all__ = [ 'Client', 'RequestFactory', 'TestCase', 'TransactionTestCase', 'SimpleTestCase', 'LiveServerTestCase', 'skipIfDBFeature', 'skipUnlessDBFeature', 'override_settings', 'skipUnlessDBFeature', 'modify_settings', 'override_settings', ]
django/test/testcases.py +20 −7 Original line number Diff line number Diff line Loading @@ -32,7 +32,7 @@ from django.test.client import Client from django.test.html import HTMLParseError, parse_html from django.test.signals import setting_changed, template_rendered from django.test.utils import (CaptureQueriesContext, ContextList, override_settings, compare_xml) override_settings, modify_settings, compare_xml) from django.utils.encoding import force_text from django.utils import six from django.utils.six.moves.urllib.parse import urlsplit, urlunsplit, urlparse, unquote Loading Loading @@ -164,7 +164,8 @@ class SimpleTestCase(unittest.TestCase): # The class we'll use for the test client self.client. # Can be overridden in derived classes. client_class = Client _custom_settings = None _overridden_settings = None _modified_settings = None def __call__(self, result=None): """ Loading Loading @@ -197,9 +198,12 @@ class SimpleTestCase(unittest.TestCase): * If the class has a 'urls' attribute, replace ROOT_URLCONF with it. * Clearing the mail test outbox. """ if self._custom_settings: self._overridden = override_settings(**self._custom_settings) self._overridden.enable() if self._overridden_settings: self._overridden_context = override_settings(**self._overridden_settings) self._overridden_context.enable() if self._modified_settings: self._modified_context = modify_settings(self._modified_settings) self._modified_context.enable() self.client = self.client_class() self._urlconf_setup() mail.outbox = [] Loading @@ -217,8 +221,10 @@ class SimpleTestCase(unittest.TestCase): * Putting back the original ROOT_URLCONF if it was changed. """ self._urlconf_teardown() if self._custom_settings: self._overridden.disable() if self._modified_settings: self._modified_context.disable() if self._overridden_settings: self._overridden_context.disable() def _urlconf_teardown(self): set_urlconf(None) Loading @@ -233,6 +239,13 @@ class SimpleTestCase(unittest.TestCase): """ return override_settings(**kwargs) def modify_settings(self, **kwargs): """ A context manager that temporarily applies changes a list setting and reverts back to the original value when exiting the context. """ return modify_settings(**kwargs) def assertRedirects(self, response, expected_url, status_code=302, target_status_code=200, host=None, msg_prefix='', fetch_redirect_response=True): Loading
django/test/utils.py +63 −12 Original line number Diff line number Diff line Loading @@ -24,8 +24,10 @@ from django.utils.translation import deactivate __all__ = ( 'Approximate', 'ContextList', 'get_runner', 'override_settings', 'requires_tz_support', 'setup_test_environment', 'teardown_test_environment', 'Approximate', 'ContextList', 'get_runner', 'modify_settings', 'override_settings', 'requires_tz_support', 'setup_test_environment', 'teardown_test_environment', ) RESTORE_LOADERS_ATTR = '_original_template_source_loaders' Loading Loading @@ -191,8 +193,6 @@ class override_settings(object): """ def __init__(self, **kwargs): self.options = kwargs # Special case that requires updating the app cache, a core feature. self.installed_apps = self.options.get('INSTALLED_APPS') def __enter__(self): self.enable() Loading @@ -207,11 +207,7 @@ class override_settings(object): raise Exception( "Only subclasses of Django SimpleTestCase can be decorated " "with override_settings") if test_func._custom_settings: test_func._custom_settings = dict( test_func._custom_settings, **self.options) else: test_func._custom_settings = self.options self.save_options(test_func) return test_func else: @wraps(test_func) Loading @@ -220,14 +216,22 @@ class override_settings(object): return test_func(*args, **kwargs) return inner def save_options(self, test_func): if test_func._overridden_settings is None: test_func._overridden_settings = self.options else: # Duplicate dict to prevent subclasses from altering their parent. test_func._overridden_settings = dict( test_func._overridden_settings, **self.options) def enable(self): override = UserSettingsHolder(settings._wrapped) for key, new_value in self.options.items(): setattr(override, key, new_value) self.wrapped = settings._wrapped settings._wrapped = override if self.installed_apps is not None: app_cache.set_installed_apps(self.installed_apps) if 'INSTALLED_APPS' in self.options: app_cache.set_installed_apps(settings.INSTALLED_APPS) for key, new_value in self.options.items(): setting_changed.send(sender=settings._wrapped.__class__, setting=key, value=new_value, enter=True) Loading @@ -235,7 +239,7 @@ class override_settings(object): def disable(self): settings._wrapped = self.wrapped del self.wrapped if self.installed_apps is not None: if 'INSTALLED_APPS' in self.options: app_cache.unset_installed_apps() for key in self.options: new_value = getattr(settings, key, None) Loading @@ -243,6 +247,53 @@ class override_settings(object): setting=key, value=new_value, enter=False) class modify_settings(override_settings): """ Like override_settings, but makes it possible to append, prepend or remove items instead of redefining the entire list. """ def __init__(self, *args, **kwargs): if args: # Hack used when instaciating from SimpleTestCase._pre_setup. assert not kwargs self.operations = args[0] else: assert not args self.operations = list(kwargs.items()) def save_options(self, test_func): if test_func._modified_settings is None: test_func._modified_settings = self.operations else: # Duplicate list to prevent subclasses from altering their parent. test_func._modified_settings = list( test_func._modified_settings) + self.operations def enable(self): self.options = {} for name, operations in self.operations: try: # When called from SimpleTestCase._pre_setup, values may be # overridden several times; cumulate changes. value = self.options[name] except KeyError: value = list(getattr(settings, name, [])) for action, items in operations.items(): # items my be a single value or an iterable. if isinstance(items, six.string_types): items = [items] if action == 'append': value = value + [item for item in items if item not in value] elif action == 'prepend': value = [item for item in items if item not in value] + value elif action == 'remove': value = [item for item in value if item not in items] else: raise ValueError("Unsupported action: %s" % action) self.options[name] = value super(modify_settings, self).enable() def compare_xml(want, got): """Tries to do a 'xml-comparison' of want and got. Plain string comparison doesn't always work because, for example, attribute Loading
docs/topics/testing/overview.txt +86 −24 Original line number Diff line number Diff line Loading @@ -1335,7 +1335,7 @@ Overriding settings For testing purposes it's often useful to change a setting temporarily and revert to the original value after running the testing code. For this use case Django provides a standard Python context manager (see :pep:`343`) Django provides a standard Python context manager (see :pep:`343`) called :meth:`~django.test.SimpleTestCase.settings`, which can be used like this:: from django.test import TestCase Loading @@ -1356,12 +1356,41 @@ Django provides a standard Python context manager (see :pep:`343`) This example will override the :setting:`LOGIN_URL` setting for the code in the ``with`` block and reset its value to the previous state afterwards. .. method:: SimpleTestCase.modify_settings .. versionadded:: 1.7 It can prove unwieldy to redefine settings that contain a list of values. In practice, adding or removing values is often sufficient. The :meth:`~django.test.SimpleTestCase.modify_settings` context manager makes it easy:: from django.test import TestCase class MiddlewareTestCase(TestCase): def test_cache_middleware(self): with self.modify_settings(MIDDLEWARE_CLASSES={ 'append': 'django.middleware.cache.FetchFromCacheMiddleware', 'prepend': 'django.middleware.cache.UpdateCacheMiddleware', 'remove': [ 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', ], }): response = self.client.get('/') # ... For each action, you can supply either a list of values or a string. When the value already exists in the list, ``append`` and ``prepend`` have no effect; neither does ``remove`` when the value doesn't exist. .. function:: override_settings In case you want to override a setting for just one test method or even the whole :class:`~django.test.TestCase` class, Django provides the :func:`~django.test.override_settings` decorator (see :pep:`318`). It's used like this:: In case you want to override a setting for a test method, Django provides the :func:`~django.test.override_settings` decorator (see :pep:`318`). It's used like this:: from django.test import TestCase, override_settings Loading @@ -1372,7 +1401,7 @@ used like this:: response = self.client.get('/sekrit/') self.assertRedirects(response, '/other/login/?next=/sekrit/') The decorator can also be applied to test case classes:: The decorator can also be applied to :class:`~django.test.TestCase` classes:: from django.test import TestCase, override_settings Loading @@ -1385,17 +1414,50 @@ The decorator can also be applied to test case classes:: .. versionchanged:: 1.7 Previously, ``override_settings`` was imported from ``django.test.utils``. Previously, ``override_settings`` was imported from ``django.test.utils``. .. function:: modify_settings .. versionadded:: 1.7 Likewise, Django provides the :func:`~django.test.modify_settings` decorator:: from django.test import TestCase, modify_settings class MiddlewareTestCase(TestCase): @modify_settings(MIDDLEWARE_CLASSES={ 'append': 'django.middleware.cache.FetchFromCacheMiddleware', 'prepend': 'django.middleware.cache.UpdateCacheMiddleware', }) def test_cache_middleware(self): response = self.client.get('/') # ... The decorator can also be applied to test case classes:: from django.test import TestCase, modify_settings @modify_settings(MIDDLEWARE_CLASSES={ 'append': 'django.middleware.cache.FetchFromCacheMiddleware', 'prepend': 'django.middleware.cache.UpdateCacheMiddleware', }) class MiddlewareTestCase(TestCase): def test_cache_middleware(self): response = self.client.get('/') # ... .. note:: When given a class, the decorator modifies the class directly and returns it; it doesn't create and return a modified copy of it. So if you try to tweak the above example to assign the return value to a different name than ``LoginTestCase``, you may be surprised to find that the original ``LoginTestCase`` is still equally affected by the decorator. When given a class, these decorators modify the class directly and return it; they don't create and return a modified copy of it. So if you try to tweak the above examples to assign the return value to a different name than ``LoginTestCase`` or ``MiddlewareTestCase``, you may be surprised to find that the original test case classes are still equally affected by the decorator. For a given class, :func:`~django.test.modify_settings` is always applied after :func:`~django.test.override_settings`. .. warning:: Loading @@ -1403,17 +1465,17 @@ The decorator can also be applied to test case classes:: initialization of Django internals. If you change them with ``override_settings``, the setting is changed if you access it via the ``django.conf.settings`` module, however, Django's internals access it differently. Effectively, using ``override_settings`` with these settings is probably not going to do what you expect it to do. differently. Effectively, using :func:`~django.test.override_settings` or :func:`~django.test.modify_settings` with these settings is probably not going to do what you expect it to do. We do not recommend using ``override_settings`` with :setting:`DATABASES`. Using ``override_settings`` with :setting:`CACHES` is possible, but a bit tricky if you are using internals that make using of caching, like We do not recommend altering the :setting:`DATABASES` setting. Altering the :setting:`CACHES` setting is possible, but a bit tricky if you are using internals that make using of caching, like :mod:`django.contrib.sessions`. For example, you will have to reinitialize the session backend in a test that uses cached sessions and overrides :setting:`CACHES`. You can also simulate the absence of a setting by deleting it after settings have been overridden, like this:: Loading @@ -1423,10 +1485,10 @@ have been overridden, like this:: ... When overriding settings, make sure to handle the cases in which your app's code uses a cache or similar feature that retains state even if the setting is changed. Django provides the :data:`django.test.signals.setting_changed` signal that lets you register callbacks to clean up and otherwise reset state when settings are changed. code uses a cache or similar feature that retains state even if the setting is changed. Django provides the :data:`django.test.signals.setting_changed` signal that lets you register callbacks to clean up and otherwise reset state when settings are changed. Django itself uses this signal to reset various data: Loading
tests/settings_tests/tests.py +57 −4 Original line number Diff line number Diff line Loading @@ -6,19 +6,57 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.http import HttpRequest from django.test import SimpleTestCase, TransactionTestCase, TestCase, signals from django.test.utils import override_settings from django.test.utils import modify_settings, override_settings from django.utils import six @override_settings(TEST='override', TEST_OUTER='outer') @modify_settings(ITEMS={ 'prepend': ['b'], 'append': ['d'], 'remove': ['a', 'e'] }) @override_settings(ITEMS=['a', 'c', 'e'], ITEMS_OUTER=[1, 2, 3], TEST='override', TEST_OUTER='outer') class FullyDecoratedTranTestCase(TransactionTestCase): available_apps = [] def test_override(self): self.assertListEqual(settings.ITEMS, ['b', 'c', 'd']) self.assertListEqual(settings.ITEMS_OUTER, [1, 2, 3]) self.assertEqual(settings.TEST, 'override') self.assertEqual(settings.TEST_OUTER, 'outer') @modify_settings(ITEMS={ 'append': ['e', 'f'], 'prepend': ['a'], 'remove': ['d', 'c'], }) def test_method_list_override(self): self.assertListEqual(settings.ITEMS, ['a', 'b', 'e', 'f']) self.assertListEqual(settings.ITEMS_OUTER, [1, 2, 3]) @modify_settings(ITEMS={ 'append': ['b'], 'prepend': ['d'], 'remove': ['a', 'c', 'e'], }) def test_method_list_override_no_ops(self): self.assertListEqual(settings.ITEMS, ['b', 'd']) @modify_settings(ITEMS={ 'append': 'e', 'prepend': 'a', 'remove': 'c', }) def test_method_list_override_strings(self): self.assertListEqual(settings.ITEMS, ['a', 'b', 'd', 'e']) @modify_settings(ITEMS={'remove': ['b', 'd']}) @modify_settings(ITEMS={'append': ['b'], 'prepend': ['d']}) def test_method_list_override_nested_order(self): self.assertListEqual(settings.ITEMS, ['d', 'c', 'b']) @override_settings(TEST='override2') def test_method_override(self): self.assertEqual(settings.TEST, 'override2') Loading @@ -31,14 +69,26 @@ class FullyDecoratedTranTestCase(TransactionTestCase): self.assertEqual(FullyDecoratedTranTestCase.__module__, __name__) @override_settings(TEST='override') @modify_settings(ITEMS={ 'prepend': ['b'], 'append': ['d'], 'remove': ['a', 'e'] }) @override_settings(ITEMS=['a', 'c', 'e'], TEST='override') class FullyDecoratedTestCase(TestCase): def test_override(self): self.assertListEqual(settings.ITEMS, ['b', 'c', 'd']) self.assertEqual(settings.TEST, 'override') @modify_settings(ITEMS={ 'append': 'e', 'prepend': 'a', 'remove': 'c', }) @override_settings(TEST='override2') def test_method_override(self): self.assertListEqual(settings.ITEMS, ['a', 'b', 'd', 'e']) self.assertEqual(settings.TEST, 'override2') Loading Loading @@ -73,14 +123,17 @@ class ClassDecoratedTestCase(ClassDecoratedTestCaseSuper): self.fail() @override_settings(TEST='override-parent') @modify_settings(ITEMS={'append': 'mother'}) @override_settings(ITEMS=['father'], TEST='override-parent') class ParentDecoratedTestCase(TestCase): pass @modify_settings(ITEMS={'append': ['child']}) @override_settings(TEST='override-child') class ChildDecoratedTestCase(ParentDecoratedTestCase): def test_override_settings_inheritance(self): self.assertEqual(settings.ITEMS, ['father', 'mother', 'child']) self.assertEqual(settings.TEST, 'override-child') Loading