Subclassing Django's TestCase
As mentioned in yet-unresolved #7835, I've been writing a sort of meta-application recently that doesn't provide any models of its own but absolutely requires models (and data) of various types to test against. I ended up adopting julien's technique, which puts the test-only application inside the tests
module, and then overrides setUp
and tearDown
to monkey patch your settings to include the test-only application and then run a syncdb
command. This approach started out working very well, but I soon ran into a fairly major problem: fixtures
failed to load.
I had by this time created a few subclasses of Django's TestCase
object, using super
to call up the chain. But my fixtures were nowhere to be found when I dropped into pdb. After messing around a bit with pdb (and looking at all the wrong stuff), I took a look at the actual definition of django's TestCase.
def __call__(self, result=None):
"""
Wrapper around default __call__ method to perform common Django test
set up. This means that user-defined Test Cases aren't required to
include a call to super().setUp().
"""
self.client = Client()
try:
self._pre_setup()
# snip exception handling...
super(TransactionTestCase, self).__call__(result)
try:
self._post_teardown()
# snip exception handling...
The way that your tests are run are roughly:
- the testrunner looks for all
unittest.TestCase
subclasses in yourapp.tests
- the testrunner instantiates them (
__init__
), then calls them (__call__
) - Django's
TestCase
attaches the test client, runs its own_pre_setup
method, then runsunittest.TestCase.__call__
- your test is run as usual (
setUp
,test_method
,tearDown
) - Django's
TestCase
runs_post_teardown
The problem with my code (and julien's in the links at the top) is that we're overriding setUp
to install the test-only application, which is called after Django's _pre_setup
, which is where the fixture loading is done. The lesson is that, if you want to create your own test baseclass and you are going to be messing with Django's settings in some way that'l affect the database, you want to reimplement _pre_setup
and _post_teardown
in your subclass.
Here's an example of a BaseTestCase class that will install a test-only application and set DEBUG to True:
from django.test import TestCase
class BaseTestCase(TestCase):
def _pre_setup(self):
self.saved_INSTALLED_APPS = settings.INSTALLED_APPS
self.saved_DEBUG = settings.DEBUG
test_app = 'foo.tests.testapp'
settings.INSTALLED_APPS = tuple(
list(self.saved_INSTALLED_APPS) + [test_app]
)
settings.DEBUG = True
# load our fake application and syncdb
load_app(test_app)
call_command('syncdb', verbosity=0, interactive=False)
super(BaseTestCase, self)._pre_setup()
def _post_teardown(self):
settings.INSTALLED_APPS = self.saved_INSTALLED_APPS
settings.DEBUG = self.saved_DEBUG
super(BaseTestCase, self)._post_teardown()