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:

  1. the testrunner looks for all unittest.TestCase subclasses in your app.tests
  2. the testrunner instantiates them (__init__), then calls them (__call__)
  3. Django's TestCase attaches the test client, runs its own _pre_setup method, then runs unittest.TestCase.__call__
  4. your test is run as usual (setUp, test_method, tearDown)
  5. 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()
Feb 15 2010