Django: How to create a model dynamically just for testing

Posted on

Question :

Django: How to create a model dynamically just for testing

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

RELATED_MODELS = ('appname1.modelname1.attribute1',
                  '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.

Asked By: muhuk


Answer #1:

You can put your tests in a tests/ subdirectory of the app (rather than a file), and include a tests/ with the test-only models.

Then provide a test-running script (example) that includes your tests/ “app” in INSTALLED_APPS. (This doesn’t work when running app tests from a real project, which won’t have the tests app in INSTALLED_APPS, but I rarely find it useful to run reusable app tests from a project, and Django 1.6+ doesn’t by default.)

(NOTE: The alternative dynamic method described below only works in Django 1.1+ if your test case subclasses TransactionTestCase – which slows down your tests significantly – and no longer works at all in Django 1.7+. It’s left here only for historical interest; don’t use it.)

At the beginning of your tests (i.e. in a setUp method, or at the beginning of a set of doctests), you can dynamically add "myapp.tests" to the INSTALLED_APPS setting, and then do this:

from 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.

Answered By: Carl Meyer

Answer #2:

@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 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:
        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
Answered By: Conley Owens

Answer #3:

I shared my solution that I use in my projects. Maybe it helps someone.

pip install django-fake-model

Two simple steps to create fake model:

1) Define model in any file (I usualy define model in test file near a test case)

from django_fake_model import models as f

class MyFakeModel(f.FakeModel):

    name = models.CharField(max_length=100)

2) Add decorator @MyFakeModel.fake_me to your TestCase or to test function.

class MyTest(TestCase):

    def test_create_model(self):
        model = MyFakeModel.objects.get(name='123')
        self.assertEqual(, '123')

This decorator creates table in your database before each test and remove the table after test.

Also you may create/delete table manually: MyFakeModel.create_table() / MyFakeModel.delete_table()

Answered By: Kirill Ermolov

Answer #4:

This solution works only for earlier versions of django (before 1.7). You can check your version easily:

import django
django.VERSION < (1, 7)

Original response:

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

  1. add 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/ and I can test it just with: ./ 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.

Answered By: paluh

Answer #5:

I’ve figured out a way for test-only models for django 1.7+.

The basic idea is, make your tests an app, and add your tests to INSTALLED_APPS.

Here’s an example:

$ ls common       fixtures tests

$ ls common/tests  

And I have different settings for different purposes(ref: splitting up the settings file), namely:

  • settings/ base settings file
  • settings/ for production
  • settings/ for development
  • settings/ for testing.

And in settings/, you can modify INSTALLED_APPS:


from default import *

DEBUG = True

INSTALLED_APPS += ['common', 'common.tests']

And make sure that you have set a proper label for your tests app, namely,


from django.apps import AppConfig

class CommonTestsConfig(AppConfig):
    name = 'common.tests'
    label = 'common_tests'

common/tests/, set up proper AppConfig(ref: Django Applications).

default_app_config = 'common.tests.apps.CommonTestsConfig'

Then, generate db migration by

python makemigrations --settings=<your_project_name>.settings.testing tests

Finally, you can run your test with param --settings=<your_project_name>.settings.testing.

If you use py.test, you can even drop a pytest.ini file along with django’s


Answered By: Xiao Hanyu

Answer #6:

I chose a slightly different, albeit more coupled, approach to dynamically creating models just for testing.

I keep all my tests in a tests subdirectory that lives in my files app. The file in the tests subdirectory contains my test-only models. The coupled part comes in here, where I need to add the following to my file:

# check if we are testing right now
TESTING = 'test' in sys.argv

    # add test packages that have models
    INSTALLED_APPS += ['files.tests',]

I also set db_table in my test model, because otherwise Django would have created the table with the name tests_<model_name>, which may have caused a conflict with other test models in another app. Here’s my my test model:

class Recipe(models.Model):

    '''Test-only model to test out thumbnail registration.'''

    dish_image = models.ImageField(upload_to='recipes/')

    class Meta:
        db_table = 'files_tests_recipe'
Answered By: Jashugan

Answer #7:

Quoting from a related answer:

If you want models defined for testing only then you should check out
Django ticket #7835 in particular comment #24 part of which
is given below:

Apparently you can simply define models directly in your
Syncdb never imports, so those models won’t get synced to the
normal db, but they will get synced to the test database, and can be
used in tests.

Answered By: joeharrie

Answer #8:

Here’s the pattern that I’m using to do this.

I’ve written this method that I use on a subclassed version of TestCase. It goes as follows:

def create_models_from_app(cls, app_name):
    Manually create Models (used only for testing) from the specified string app name.
    Models are loaded from the module "<app_name>.models"
    from django.db import connection, DatabaseError
    from django.db.models.loading import load_app

    app = load_app(app_name)
    from import sql
    from import no_style
    sql = sql.sql_create(app, no_style(), connection)
    cursor = connection.cursor()
    for statement in sql:
        except DatabaseError, excn:

Then, I create a special test-specific file in something like myapp/tests/ that’s not included in INSTALLED_APPS.

In my setUp method, I call create_models_from_app(‘myapp.tests’) and it creates the proper tables.

The only “gotcha” with this approach is that you don’t really want to create the models ever time setUp runs, which is why I catch DatabaseError. I guess the call to this method could go at the top of the test file and that would work a little better.

Answered By: slacy

Leave a Reply

Your email address will not be published. Required fields are marked *