[python] Django: How to create a model dynamically just for testing
@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
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
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.
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:
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
models.py 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
# check if we are testing right now TESTING = 'test' in sys.argv if TESTING: # 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'
If you are writing a reusable django-app, create a minimal test-dedicated app for it!
$ django-admin.py startproject test_myapp_project $ django-admin.py startapp test_myapp
test_myapp to the
INSTALLED_APPS, create your models there and it's good to go!
I have gone through all these answers as well as django ticket 7835, and I finally went for a totally different approach. I wanted my app (somehow extending queryset.values() ) to be able to be tested in isolation; also, my package does include some models and I wanted a clean distinction between test models and package ones.
That's when I realized it was easier to add a very small django project in the package! This also allows a much cleaner separation of code IMHO:
In there you can cleanly and without any hack define your models, and you know they will be created when you run your tests from in there!
If you are not writing an independent, reusable app you can still go this way: create a
test_myapp app, and add it to your INSTALLED_APPS only in a separate
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:
@classmethod 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 django.core.management import sql from django.core.management.color import no_style sql = sql.sql_create(app, no_style(), connection) cursor = connection.cursor() for statement in sql: try: cursor.execute(statement) except DatabaseError, excn: logger.debug(excn.message) pass
Then, I create a special test-specific models.py file in something like
myapp/tests/models.py 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.