Django: How to create a model dynamically just for testing
Solution 1
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.
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 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.
Solution 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 lets 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
Solution 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):
@MyFakeModel.fake_me
def test_create_model(self):
MyFakeModel.objects.create(name='123')
model = MyFakeModel.objects.get(name='123')
self.assertEqual(model.name, '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()
Solution 4
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
__init__.py admin.py apps.py fixtures models.py pagination.py tests validators.py views.py
$ ls common/tests
__init__.py apps.py models.py serializers.py test_filter.py test_pagination.py test_validators.py views.py
And I have different settings
for different purposes(ref: splitting up the settings file), namely:
-
settings/default.py
: base settings file -
settings/production.py
: for production -
settings/development.py
: for development -
settings/testing.py
: for testing.
And in settings/testing.py
, you can modify INSTALLED_APPS
:
settings/testing.py
:
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,
common/tests/apps.py
from django.apps import AppConfig
class CommonTestsConfig(AppConfig):
name = 'common.tests'
label = 'common_tests'
common/tests/__init__.py
, set up proper AppConfig
(ref: Django Applications).
default_app_config = 'common.tests.apps.CommonTestsConfig'
Then, generate db migration by
python manage.py 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 manage.py
.
py.test
[pytest]
DJANGO_SETTINGS_MODULE=kungfu.settings.testing
Solution 5
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:
- add tests.py to app which you are going to test,
- in this file just define testing models,
- 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.
Comments
-
muhuk over 2 years
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.
-
muhuk over 15 yearsThat'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.
-
Carl Meyer over 15 yearsYeah, 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".
-
cethegeek over 14 yearsCarl, thanks for the snippet. I was about to go write this when I found this page and the link. Good stuff.
-
adamnfish almost 14 yearsThis 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 almost 14 yearsUpdate - 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
-
VIGNESH over 13 yearsTo get this to work with South, I had to pass
migrate=False
to call_command. -
Timmy O'Mahony almost 13 yearsthis is great. I was just about to ask this question before I found this answer. Thanks for the post
-
muhuk over 12 yearsThis would work well for a project, but probably not for an app. Clean approach though.
-
Jashugan over 12 yearsThat's true. I was thinking that if Django shipped with the ability to have setting files in apps, then this would work without having to make project level modifications.
-
marue about 12 yearsIf you have defined settings.INSTALLED_APPS as a tuple (like proposed in the django docs) you first have to convert it to a list as well. Otherwise it works fine.
-
Aron Griffis almost 12 yearsThe link for the example test-running script is dead; here's the updated link
-
zVictor almost 12 yearsWhere is this logger imported from? I am having this issue: NameError: global name 'logger' is not defined
-
slacy over 11 years
import logging; logger = logging.getLogger(__name__)
-
Mike Shultz over 10 yearsWell, lots of apps take into account project settings files. There's also the option of something like this: github.com/jaredly/django-appsettings
-
Zulu over 9 yearsWhich version of Django do you use ?
-
Sarah Messer about 9 yearsThis seems to have become less reliable in Django 1.7+, presumably because of the way migrations are being handled.
-
Raffi over 7 years@Sarah: can you elaborate on that?
-
Sarah Messer over 7 yearsDjango 1.7+ doesn't have "syncdb". It's been at least a year since I investigated, but if I recall correctly, AppConfig.ready() is only called after the DB has been built and all migrations run, and the tests module isn't even loaded until after AppConfig.ready(). You might be able to hack something with a custom test runner, settings.py, or AppConfig, but I was not able to get obvious variants of put-the-models-in-the-tests to work. If someone has a Django 1.7+ example of this working, I'd be happy to see it.
-
Fallen Flint over 7 yearsThis approach seems not to work at least in Django 1.9.1: There is no django.db.models.loading anymore
-
Fallen Flint over 7 yearsNice approach, seems to be the only working solution in recent Django versions. But it's worth mention that DEBUG=False for testing regardless your settings.py
-
Carl Meyer over 7 yearsYes. The answer specifically says the dynamic approach doesn't work in Django 1.7+ and is left here only for historical interest. The right approach is outlined in the first paragraph, and still works fine.
-
Xiao Hanyu over 7 yearsWhy would you like
DEBUG=False
intesting.py
? -
Fallen Flint over 7 yearsnot sure I got you right. It's not my desire, it's the default behavior of Django
-
Alex Paramonov about 7 yearsThe only solution at the moment that works in Django 1.10, thanks!
-
Routhinator over 5 yearsI cannot get the approach outlined in the first paragraph to work in Django 2.0+ - Seems as though this entire answer is now deprecated.
-
djvg over 5 years@Routhinator, maybe the django docs can help. They provide an example with dedicated
tests/models.py
andtests/test_settings.py
. -
Routhinator over 5 yearsThey do, and I've mirrored their setup, but it doesn't work as expected.
-
Amir Shabani about 2 years@AronGriffis The updated link is also dead.