Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Missing Table When Running Django Unittest with Sqlite3

Tags:

I'm trying to run a unittest with Django 1.3. Normally, I use MySQL as my database backend, but since this is painfully slow to spinup for a single unittest, I'm using Sqlite3.

So to switch to Sqlite3 just for my unittests, in my settings.py I have:

import sys
if 'test' in sys.argv:
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.sqlite3',
            'NAME':'/tmp/database.db',
            'USER'       : '',
            'PASSWORD' : '',
            'HOST'     : '',
        }
    }

When I run my unittest with python manage.py test myapp.Test.test_myfunc, I get the error:

DatabaseError: no such table: django_content_type

Googling shows there are a few of possible reasons for this error, none of which seem applicable to me. I'm not running Apache, so I don't see how permissions would be an issue. The file /tmp/database.db is being created, so /tmp is writable. The app django.contrib.contenttypes is included in my INSTALLED_APPS.

What am I missing?

Edit: I ran into this problem again in Django 1.5, but none of the proposed solutions work.

like image 1000
Cerin Avatar asked Sep 07 '11 15:09

Cerin


1 Answers

In Django 1.4, 1.5, 1.6, 1.7, or 1.8 it should be sufficient to use:

if 'test' in sys.argv:
    DATABASES['default']['ENGINE'] = 'django.db.backends.sqlite3'

It should not be necessary to override TEST_NAME1, nor to call syncdb in order to run tests. As @osa points out, the default with the SQLite engine is to create the test database in memory (TEST_NAME=':memory:'). Calling syncdb should not be necessary because Django's test framework will do this automatically via a call to syncdb or migrate depending on the Django version.2 You can observe this with manage.py test -v [2|3].

Very loosely speaking Django sets up the test environment by:

  1. Loading the regular database NAME from your settings.py
  2. Discovering and constructing your test classes (__init__() is called)
  3. Setting the database NAME to the value of TEST_NAME
  4. Running the tests against the database NAME

Here's the rub: At step 2, NAME is still pointing at your regular (non-test) database. If your tests contain class-level queries or queries in __init__(), they will be run against the regular database which is likely not what you are expecting. This is identified in bug #21143.

Don't do:

class BadFooTests(TestCase):
    Foo.objects.all().delete()     # <-- class level queries, and

    def __init__(self):
        f = Foo.objects.create()   # <-- queries in constructor
        f.save()                   #     will run against the production DB

    def test_foo(self):
        # assert stuff

since these will be run against the database specified in NAME. If NAME at this stage points to a valid database (e.g. your production database), the query will run, but may have unintended consequences. If you have overridden ENGINE and/or NAME such that it does not point to a pre-existing database, an exception will be thrown because the test database has yet to be created:

django.db.utils.DatabaseError: no such table: yourapp_foo  # Django 1.4
DatabaseError: no such table: yourapp_foo                  # Django 1.5
OperationalError: no such table: yourapp_foo               # Django 1.6+

Instead do:

class GoodFooTests(TestCase):

    def setUp(self):
        f = Foo.objects.create()   # <-- will run against the test DB
        f.save()                   #

    def test_foo(self):
        # assert stuff

So, if you are seeing errors, check to see that your tests do not include any queries that might hit the database outside of your test class method definitions.


[1] In Django >= 1.7, DATABASES[alias]['TEST_NAME'] is deprecated in favour of DATABASES[alias]['TEST']['NAME']
[2] See the create_test_db() method in db/backends/creation.py

like image 121
StvnW Avatar answered Oct 23 '22 02:10

StvnW