views:

966

answers:

3

I have a Django app that requires a settings attribute in the form of:

RELATED_MODELS = ('appname1.modelname1.attribute1',
                  'appname1.modelname2.attribute2', 
                  'appname2.modelname3.attribute3', ...)

Then hooks their post_save signal to update some other fixed model depending on the attributeN defined.

I would like to test this behaviour and tests should work even if this app is the only one in the project (except for its own dependencies, no other wrapper app need to be installed). How can I create and attach/register/activate mock models just for the test database? (or is it possible at all?)

Solutions that allow me to use test fixtures would be great.

+17  A: 

(NOTE: The method described here only works in Django 1.1+ if your test case subclasses TransactionTestCase. Since this will slow your tests considerably, I don't recommend it. Instead, place your models in tests/models.py as described below, and provide a test-running script (example) that just includes that tests/ "app" in INSTALLED_APPS from the beginning. Yes, this doesn't work when running app tests from a project, but I rarely find that useful for reusable apps anyway.)

You can put your tests in a tests/ subdirectory of the app (rather than a tests.py file), and include a tests/models.py with the test-only models. At the beginning of your tests (i.e. in a setUp method, or at the beginning of a set of doctests), you'll need to dynamically add "myapp.tests" to the INSTALLED_APPS setting, and then do this:

from django.core.management import call_command
from django.db.models import loading
loading.cache.loaded = False
call_command('syncdb', verbosity=0)

Then at the end of your tests, you should clean up by restoring the old version of INSTALLED_APPS and clearing the app cache again.

This class encapsulates the pattern so it doesn't clutter up your test code quite as much.

Carl Meyer
That's a clean and powerful snipplet (I guess it's yours). Creating a whole app at first seemed like too much just for a mock model. But now I think it represents real world usage best from a unit testing perspective. Thanks.
muhuk
Yeah, I don't know what's best, but this works for me. "Creating a whole app" seems like a lot less of a big deal when you realize that all it really means is "create a models.py file".
Carl Meyer
Carl, thanks for the snippet. I was about to go write this when I found this page and the link. Good stuff.
celopes
+3  A: 

It's quite strange but form me works very simple pattern:

  1. add tests.py to app which you are going to test,
  2. in this file just define testing models,
  3. below put your testing code (doctest or TestCase definition),

Below I've put some code which defines Article model which is needed only for tests (it exists in someapp/tests.py and I can test it just with: ./manage.py test someapp ):

class Article(models.Model):
    title = models.CharField(max_length=128)
    description = models.TextField()
    document = DocumentTextField(template=lambda i: i.description)

    def __unicode__(self):
        return self.title

__test__ = {"doctest": """
#smuggling model for tests
>>> from .tests import Article

#testing data
>>> by_two = Article.objects.create(title="divisible by two", description="two four six eight")
>>> by_three = Article.objects.create(title="divisible by three", description="three six nine")
>>> by_four = Article.objects.create(title="divisible by four", description="four four eight")

>>> Article.objects.all().search(document='four')
[<Article: divisible by two>, <Article: divisible by four>]
>>> Article.objects.all().search(document='three')
[<Article: divisible by three>]
"""}

Unit tests also working with such model definition.

paluh
This doesn't work for me in 1.1.1
Esteban Feldman
I amend myself... it works... thanks
Esteban Feldman
This is great - works fine (I'm using django 1.2.1) and this feels like the 'right' way to do it to me. The test model should exist as part of the tests for this application.
adamnfish
Update - this doesn't work for fixtures but you can call syndb manually (via call_command) by overriding _pre_setup as described in Conley's answer to this question
adamnfish
+1  A: 

@paluh's answer requires adding unwanted code to a non-test file and in my experience, @carl's solution does not work with django.test.TestCase which is needed to use fixtures. If you want to use django.test.TestCase, you need to make sure you call syncdb before the fixtures get loaded. This requires overriding the _pre_setup method (putting the code in the setUp method is not sufficient). I use my own version of TestCase that let's me add apps with test models. It is defined as follows:

from django.conf import settings
from django.core.management import call_command
from django.db.models import loading
from django import test

class TestCase(test.TestCase):
    apps = ()

    def _pre_setup(self):
        # Add the models to the db.
        self._original_installed_apps = list(settings.INSTALLED_APPS)
        for app in self.apps:
            settings.INSTALLED_APPS.append(app)
        loading.cache.loaded = False
        call_command('syncdb', interactive=False, verbosity=0)
        # Call the original method that does the fixtures etc.
        super(TestCase, self)._pre_setup()

    def _post_teardown(self):
        # Call the original method.
        super(TestCase, self)._post_teardown()
        # Restore the settings.
        settings.INSTALLED_APPS = self._original_installed_apps
        loading.cache.loaded = False
Conley Owens
great stuff Conley!
kRON