Django: how to create a model dynamically just for testing

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

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

Then it intercepts its post_save signal to update some other fixed model depending on attributeN .

I would like to test this behavior, and tests should work even if this application is the only one in the project (with the exception of its own dependencies, another shell application should not be installed). How can I create and attach / register / activate mock models only for a test database? (or is it even possible?)

The solutions that allow me to use test devices would be great.

+50
python django unit-testing django-models mocking
Feb 02 '09 at 11:35
source share
10 answers

You can put your tests in the tests/ application subdirectory (and not the tests.py file) and enable tests/models.py only with test models.

Then specify a test script ( example ) that includes tests/ "application" in INSTALLED_APPS . (This does not work when running application tests from a real project, which does not have an application for tests in INSTALLED_APPS , but it is rarely useful for me to run reusable application tests from a project, and Django 1.6+ by default.)

( NOTE : The alternative dynamic method described below only works in Django 1.1+, if the test tec TransactionTestCase subclasses significantly slow down your tests and no longer work in Django 1.7+. It stayed here only for historical interest, do not use it.)

At the beginning of your tests (that is, in the setUp method or at the beginning of a set of doctrines), 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 clear by restoring the old version of INSTALLED_APPS and clearing the application cache again.

This class encapsulates the template so that it does not clutter up your test code in the same way.

+45
Feb 02 '09 at 14:49
source share

The @paluh answer requires adding unwanted code to the file without testing, and, in my experience, the @carl solution does not work with django.test.TestCase, which is necessary for using devices. If you want to use django.test.TestCase, you need to make sure that you call syncdb before loading the fixtures. This requires overriding the _pre_setup method (putting code in the setUp method is not enough). I use my own version of TestCase, which allows me to add applications 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 
+17
Apr 20 '10 at 4:00
source share

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

 import django django.VERSION < (1, 7) 

Original answer:

This is rather strange, but the form of me works very simply:

  • add test.py to the application you are going to test,
  • test models are simply defined in this file,
  • enter your test code below (doctest or TestCase definition)

Below I put some code that defines the model of the article, which is necessary only for tests (it exists in someapp / tests.py, and I can check it only 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 work with this model definition.

+10
Dec 01 '09 at 16:24
source share

I chose a slightly different, albeit closer approach to the dynamic creation of models for testing only.

I save all my tests in the tests subdirectory that lives in my files application. The models.py file in the tests subdirectory contains my test models. The related part appears here where I need to add the following to my settings.py file:

 # 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 create a table called tests_<model_name> , which could cause a conflict with other test models in another application. Here is my test model of mine:

 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' 
+8
Jan 03 '12 at 22:10
source share

Quote from a related answer :

If you need models specific for testing, then you should check out Django ticket # 7835 , in particular comment # 24 of which is given below:

Apparently, you can simply define the models directly in test.py. Syncdb never imports tests.py, so these models will not synchronize with normal db, but they will synchronize with the test database and can be used in tests.

+8
Mar 17 '13 at 12:34
source share

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

pip install django-fake-model

Two simple steps to create a fake model:

1) Define the model in any file (I usually define the model in the test file near the test case)

 from django_fake_model import models as f class MyFakeModel(f.FakeModel): name = models.CharField(max_length=100) 

2) Add the @MyFakeModel.fake_me decorator to TestCase or to the 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 a table in your database before each test and deletes the table after the test.

You can also create / delete a table manually: MyFakeModel.create_table() / MyFakeModel.delete_table()

+6
Sep 28 '15 at 14:17
source share

I developed a way for test models for django 1.7+.

The basic idea: make your tests application and add tests to INSTALLED_APPS .

Here is 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 (link: splitting the settings file ), namely:

  • settings/default.py : basic settings file
  • settings/production.py : for production
  • settings/development.py : for development
  • settings/testing.py : for testing.

And in settings/testing.py you can change INSTALLED_APPS :

settings/testing.py :

 from default import * DEBUG = True INSTALLED_APPS += ['common', 'common.tests'] 

And make sure you set the proper label for your test application, namely

common/tests/apps.py

 from django.apps import AppConfig class CommonTestsConfig(AppConfig): name = 'common.tests' label = 'common_tests' 

common/tests/__init__.py , install the correct AppConfig (ref: Django Applications ).

 default_app_config = 'common.tests.apps.CommonTestsConfig' 

Then generate the db migration to

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

Finally, you can run the test with the parameter --settings=<your_project_name>.settings.testing .

If you use py.test, you can even delete the pytest.ini file along with django manage.py .

py.test

 [pytest] DJANGO_SETTINGS_MODULE=kungfu.settings.testing 
+6
Jan 20 '16 at 13:08
source share

Here is the template I use for this.

I wrote this method, which I use in a subclass version of TestCase. This happens 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 the form of myapp/tests/models.py , which is not included in INSTALLED_APPS.

In my setUp method, I call create_models_from_app ('myapp.tests') and create the correct tables.

The only β€œobtained" with this approach is that you really do not want to create models during setUp execution, so I will catch DatabaseError. I assume that calling this method may be at the top of the test file, and this will work a little better.

+3
Apr 18 2018-12-18T00:
source share

Combining your answers, especially @ slacy's, I did this:

 class TestCase(test.TestCase): initiated = False @classmethod def setUpClass(cls, *args, **kwargs): if not TestCase.initiated: TestCase.create_models_from_app('myapp.tests') TestCase.initiated = True super(TestCase, cls).setUpClass(*args, **kwargs) @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) 

However, you are not trying to create db tables more than once, and you do not need to change INSTALLED_APPS.

+2
May 18 '12 at 18:13
source share

If you are writing a reusable django application, create a minimal test application for it !

 $ django-admin.py startproject test_myapp_project $ django-admin.py startapp test_myapp 

add both test_myapp and test_myapp to INSTALLED_APPS , create your models there, and that’s good!

I went through all these answers, as well as django ticket 7835 , and I finally took a completely different approach. I wanted my application (somehow extended queryset.values ​​()) to be able to be checked in isolation; In addition, my package includes some models, and I wanted to make a clean distinction between test models and packages.

This, as I understood it, was easier to add a very small django project to the package! It also allows for a much clearer separation of IMHO code:

There you can easily and without any hacking define your models, and you know that they will be created when you run your tests there!

If you do not create an independent, reusable application, you will still go as follows: create the test_myapp application and add it to your INSTALLED_APPS only in a separate settings_test_myapp.py !

0
Jul 23 '14 at 10:10
source share



All Articles