[Fixed]-Django tests complain of missing tables

25👍

Since you’re using a legacy database, you are probably not adding the app name to INSTALLED_APPS. If an app is not included in INSTALLED_APPS, the tables for the apps’ models will not get created on syncdb. This works for you in production since you already have a table, but not in test environment.

You can adopt any of the following:

  • The supermonkeypatch way: Take out app_name from Customer class Meta, put the model in a models.py file inside a python module name mcif, and add mcif to INSTALLED_APPS – just for the sake of testing

  • The nicer way: Extend DjangoTestSuiteRunner and override setup_test_environment to call super and then create your legacy table manually in the test DB.

  • The nicest way: Put your model in properly named app module. Remove app_name from model Meta but add managed=False docs. Include app name in INSTALLED_APPS. Now django will not create table for that model. Then use this nice snippet the Caktus group folks have compiled to run your tests.

Cheers!

Edit – How to use the overridden DjangoTestSuiteRunner

You will need at least Django 1.2 for this.

Copy the code from here. Put it in utils.py inside the mcif app.

Add/edit the following in settings.py:

TEST_RUNNER = 'mcif.utils.ManagedModelTestRunner'

Now when you run tests, all unmanaged tables will be treated as managed table only for the duration of the test. So the tables will be created prior to running tests.

Notice this part of the code, thats where the magic happens.

self.unmanaged_models = [m for m in get_models() if not m._meta.managed]
for m in self.unmanaged_models:
    m._meta.managed = True

2nd Edit: Possible Gotchas

Make sure of the following:

  • The DB user has privilege to create databases and not only tables because django will try to create a test database
  • The test cases extend django.test.TransactionTestCase, since you have transactional behavior
  • If none of the above applies, put a pdb in ManagedModelTestRunner’s setup_test_environment just to make sure the code is being reached. Because if that code is reached, the table should get created

3rd Edit: Debugging
Inside mcif.utils.ManagedModelTestRunner replace setup_test_environment function with the following and let me know if the output of your test changes:

def setup_test_environment(self, *args, **kwargs):
    print "Loading ManagedModelTestRunner"
    from django.db.models.loading import get_models
    self.unmanaged_models = [m for m in get_models()
                             if not m._meta.managed]
    for m in self.unmanaged_models:
        print "Modifying model %s to be managed for testing" % m
        m._meta.managed = True
    super(ManagedModelTestRunner, self).setup_test_environment(*args, **kwargs)

6👍

The solutions presented by tarequeh worked for me after overriding DATABASE_ROUTERS.

I am using routers in order to prevent writes on the legacy database. In order to get around this I created a test_settings file with the following contents:

from settings import *

DEBUG = True

TEST_RUNNER = 'legacy.utils.ManagedModelTestRunner'

DATABASE_ROUTERS = []

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(HERE, 'test.db'),
    },
}

Then when running tests:

python manage.py test [app_name] --settings=test_settings

2👍

There’s not enough info above to answer your first question. However, once you get that issue resolved you’ll probably want to install django-extensions for the following reason: It has an incredibly useful sqldiff command that will inform you if there’s a mismatch between the legacy database and your application model.

1👍

Here’s a more up-to-date solution, that also works with current versions of Django (I tested it on Django 3.2.11):
https://medium.com/an-idea/testing-with-the-legacy-database-in-django-3be84786daba

Also, in case you want to furthermore populate the Django test-database with your legacy database’s data:
Check out fixtures

Leave a comment