diff --git a/django/test/testcases.py b/django/test/testcases.py index 566c9395fb..9b25a0b937 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -161,6 +161,24 @@ class SimpleTestCase(unittest.TestCase): _overridden_settings = None _modified_settings = None + @classmethod + def setUpClass(cls): + if cls._overridden_settings: + cls._cls_overridden_context = override_settings(**cls._overridden_settings) + cls._cls_overridden_context.enable() + if cls._modified_settings: + cls._cls_modified_context = modify_settings(cls._modified_settings) + cls._cls_modified_context.enable() + + @classmethod + def tearDownClass(cls): + if hasattr(cls, '_cls_modified_context'): + cls._cls_modified_context.disable() + delattr(cls, '_cls_modified_context') + if hasattr(cls, '_cls_overridden_context'): + cls._cls_overridden_context.disable() + delattr(cls, '_cls_overridden_context') + def __call__(self, result=None): """ Wrapper around default __call__ method to perform common Django test @@ -192,24 +210,18 @@ class SimpleTestCase(unittest.TestCase): * If the class has a 'urls' attribute, replace ROOT_URLCONF with it. * Clearing the mail test outbox. """ - 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 = [] def _urlconf_setup(self): - set_urlconf(None) if hasattr(self, 'urls'): warnings.warn( "SimpleTestCase.urls is deprecated and will be removed in " "Django 2.0. Use @override_settings(ROOT_URLCONF=...) " "in %s instead." % self.__class__.__name__, RemovedInDjango20Warning, stacklevel=2) + set_urlconf(None) self._old_root_urlconf = settings.ROOT_URLCONF settings.ROOT_URLCONF = self.urls clear_url_caches() @@ -220,14 +232,10 @@ class SimpleTestCase(unittest.TestCase): * Putting back the original ROOT_URLCONF if it was changed. """ self._urlconf_teardown() - if self._modified_settings: - self._modified_context.disable() - if self._overridden_settings: - self._overridden_context.disable() def _urlconf_teardown(self): - set_urlconf(None) if hasattr(self, '_old_root_urlconf'): + set_urlconf(None) settings.ROOT_URLCONF = self._old_root_urlconf clear_url_caches() @@ -1169,6 +1177,7 @@ class LiveServerTestCase(TransactionTestCase): @classmethod def setUpClass(cls): + super(LiveServerTestCase, cls).setUpClass() connections_override = {} for conn in connections.all(): # If using in-memory sqlite databases, pass the connections to diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index 00a2c9844c..875f17f40f 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -919,3 +919,11 @@ to construct the "view on site" URL. This URL is now accessible using the ``FormMixin`` subclasses that override the ``get_form()`` method should make sure to provide a default value for the ``form_class`` argument since it's now optional. + +Overriding ``setUpClass`` / ``tearDownClass`` in test cases +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The decorators :func:`~django.test.override_settings` and +:func:`~django.test.modify_settings` now act at the class level when used as +class decorators. As a consequence, when overriding ``setUpClass()`` or +``tearDownClass()``, the ``super`` implementation should always be called. diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt index 8049d62761..1ff0d15352 100644 --- a/docs/topics/testing/tools.txt +++ b/docs/topics/testing/tools.txt @@ -607,6 +607,25 @@ then you should use :class:`~django.test.TransactionTestCase` or ``SimpleTestCase`` inherits from ``unittest.TestCase``. +.. warning:: + + ``SimpleTestCase`` and its subclasses (e.g. ``TestCase``, ...) rely on + ``setUpClass()`` and ``tearDownClass()`` to perform some class-wide + initialization (e.g. overriding settings). If you need to override those + methods, don't forget to call the ``super`` implementation:: + + class MyTestCase(TestCase): + + @classmethod + def setUpClass(cls): + super(cls, MyTestCase).setUpClass() # Call parent first + ... + + @classmethod + def tearDownClass(cls): + ... + super(cls, MyTestCase).tearDownClass() # Call parent last + TransactionTestCase ~~~~~~~~~~~~~~~~~~~ @@ -751,8 +770,8 @@ Then, add a ``LiveServerTestCase``-based test to your app's tests module @classmethod def setUpClass(cls): - cls.selenium = WebDriver() super(MySeleniumTests, cls).setUpClass() + cls.selenium = WebDriver() @classmethod def tearDownClass(cls): diff --git a/tests/cache/tests.py b/tests/cache/tests.py index 3ad1979af4..c7604f131e 100644 --- a/tests/cache/tests.py +++ b/tests/cache/tests.py @@ -28,6 +28,7 @@ from django.middleware.csrf import CsrfViewMiddleware from django.template import Template from django.template.response import TemplateResponse from django.test import TestCase, TransactionTestCase, RequestFactory, override_settings +from django.test.signals import setting_changed from django.test.utils import IgnoreDeprecationWarningsMixin from django.utils import six from django.utils import timezone @@ -1144,8 +1145,12 @@ class FileBasedCacheTests(BaseCacheTests, TestCase): def setUp(self): super(FileBasedCacheTests, self).setUp() self.dirname = tempfile.mkdtemp() + # Caches location cannot be modified through override_settings / modify_settings, + # hence settings are manipulated directly here and the setting_changed signal + # is triggered manually. for cache_params in settings.CACHES.values(): cache_params.update({'LOCATION': self.dirname}) + setting_changed.send(self.__class__, setting='CACHES', enter=False) def tearDown(self): super(FileBasedCacheTests, self).tearDown() diff --git a/tests/file_uploads/tests.py b/tests/file_uploads/tests.py index fe1a79fc9f..4fa75415ec 100644 --- a/tests/file_uploads/tests.py +++ b/tests/file_uploads/tests.py @@ -33,12 +33,14 @@ class FileUploadTests(TestCase): @classmethod def setUpClass(cls): + super(FileUploadTests, cls).setUpClass() if not os.path.isdir(MEDIA_ROOT): os.makedirs(MEDIA_ROOT) @classmethod def tearDownClass(cls): shutil.rmtree(MEDIA_ROOT) + super(FileUploadTests, cls).tearDownClass() def test_simple_upload(self): with open(__file__, 'rb') as fp: @@ -494,12 +496,14 @@ class DirectoryCreationTests(TestCase): """ @classmethod def setUpClass(cls): + super(DirectoryCreationTests, cls).setUpClass() if not os.path.isdir(MEDIA_ROOT): os.makedirs(MEDIA_ROOT) @classmethod def tearDownClass(cls): shutil.rmtree(MEDIA_ROOT) + super(DirectoryCreationTests, cls).tearDownClass() def setUp(self): self.obj = FileModel() diff --git a/tests/mail/tests.py b/tests/mail/tests.py index a6184bd767..9431077e06 100644 --- a/tests/mail/tests.py +++ b/tests/mail/tests.py @@ -855,6 +855,7 @@ class SMTPBackendTests(BaseEmailBackendTests, SimpleTestCase): @classmethod def setUpClass(cls): + super(SMTPBackendTests, cls).setUpClass() cls.server = FakeSMTPServer(('127.0.0.1', 0), None) cls._settings_override = override_settings( EMAIL_HOST="127.0.0.1", @@ -866,6 +867,7 @@ class SMTPBackendTests(BaseEmailBackendTests, SimpleTestCase): def tearDownClass(cls): cls._settings_override.disable() cls.server.stop() + super(SMTPBackendTests, cls).tearDownClass() def setUp(self): super(SMTPBackendTests, self).setUp() diff --git a/tests/servers/tests.py b/tests/servers/tests.py index 2331a0ddce..50b2cafe1a 100644 --- a/tests/servers/tests.py +++ b/tests/servers/tests.py @@ -27,7 +27,7 @@ TEST_SETTINGS = { } -@override_settings(ROOT_URLCONF='servers.urls') +@override_settings(ROOT_URLCONF='servers.urls', **TEST_SETTINGS) class LiveServerBase(LiveServerTestCase): available_apps = [ @@ -38,19 +38,6 @@ class LiveServerBase(LiveServerTestCase): ] fixtures = ['testdata.json'] - @classmethod - def setUpClass(cls): - # Override settings - cls.settings_override = override_settings(**TEST_SETTINGS) - cls.settings_override.enable() - super(LiveServerBase, cls).setUpClass() - - @classmethod - def tearDownClass(cls): - # Restore original settings - cls.settings_override.disable() - super(LiveServerBase, cls).tearDownClass() - def urlopen(self, url): return urlopen(self.live_server_url + url) diff --git a/tests/settings_tests/tests.py b/tests/settings_tests/tests.py index c679962a99..64f99fc859 100644 --- a/tests/settings_tests/tests.py +++ b/tests/settings_tests/tests.py @@ -106,9 +106,19 @@ class ClassDecoratedTestCaseSuper(TestCase): @override_settings(TEST='override') class ClassDecoratedTestCase(ClassDecoratedTestCaseSuper): + + @classmethod + def setUpClass(cls): + super(cls, ClassDecoratedTestCase).setUpClass() + cls.foo = getattr(settings, 'TEST', 'BUG') + def test_override(self): self.assertEqual(settings.TEST, 'override') + def test_setupclass_override(self): + """Test that settings are overriden within setUpClass -- refs #21281""" + self.assertEqual(self.foo, 'override') + @override_settings(TEST='override2') def test_method_override(self): self.assertEqual(settings.TEST, 'override2') diff --git a/tests/utils_tests/test_tzinfo.py b/tests/utils_tests/test_tzinfo.py index 1b007f5f76..4d1af62f0a 100644 --- a/tests/utils_tests/test_tzinfo.py +++ b/tests/utils_tests/test_tzinfo.py @@ -20,6 +20,7 @@ class TzinfoTests(IgnoreDeprecationWarningsMixin, unittest.TestCase): @classmethod def setUpClass(cls): + super(TzinfoTests, cls).setUpClass() cls.old_TZ = os.environ.get('TZ') os.environ['TZ'] = 'US/Eastern' @@ -41,6 +42,7 @@ class TzinfoTests(IgnoreDeprecationWarningsMixin, unittest.TestCase): # Cleanup - force re-evaluation of TZ environment variable. if cls.tz_tests: time.tzset() + super(TzinfoTests, cls).tearDownClass() def test_fixedoffset(self): self.assertEqual(repr(FixedOffset(0)), '+0000')