Django unit without db

Is it possible to write django unittests without setting up db? I want to test business logic that does not require db configuration. And while installing db quickly, I really don't need it in some situations.

+96
django testing
May 6 '11 at 22:16
source share
11 answers

You can subclass DjangoTestSuiteRunner and override the setup_databases and teardown_database methods to pass.

Create a new settings file and set TEST_RUNNER for the new class you just created. Then, when you run the test, specify a new settings file with the --settings flag.

Here is what I did:

Create a custom test suit runner similar to this:

from django.test.simple import DjangoTestSuiteRunner class NoDbTestRunner(DjangoTestSuiteRunner): """ A test runner to test without database creation """ def setup_databases(self, **kwargs): """ Override the database creation defined in parent class """ pass def teardown_databases(self, old_config, **kwargs): """ Override the database teardown defined in parent class """ pass 

Create custom settings:

 from mysite.settings import * # Test runner with no database creation TEST_RUNNER = 'mysite.scripts.testrunner.NoDbTestRunner' 

When you run your tests, run them, as shown below, with the --settings flag set for your new settings file:

 python manage.py test myapp --settings='no_db_settings' 

UPDATE: April / 2018

Starting with Django 1.8, the django.test.simple.DjangoTestSuiteRunner module django.test.simple.DjangoTestSuiteRunner been moved to 'django.test.runner.DiscoverRunner' .

For more information, check the official documentation section for custom tests.

+102
Aug 10 2018-11-11T00:
source share

Typically, in-app tests can be classified into two categories.

  • Unit tests, they check individual pieces of code in insolation and do not require a transition to the database.
  • Integration test cases that actually go to the database and test the fully integrated logic.

Django supports both unit and integration tests.

Unit tests that do not require database installation and disruption, and we must inherit from SimpleTestCase.

 from django.test import SimpleTestCase class ExampleUnitTest(SimpleTestCase): def test_something_works(self): self.assertTrue(True) 

For test cases of integration, inheritance from TestCase, in turn, is inherited from TransactionTestCase, and it will set and reset the database before running each test.

 from django.test import TestCase class ExampleIntegrationTest(TestCase): def test_something_works(self): #do something with database self.assertTrue(True) 

This strategy ensures that the database created and destroyed only for test cases that access the database and therefore the tests will be more efficient

+52
Jan 09 '14 at 5:11
source share

From django.test.simple

  warnings.warn( "The django.test.simple module and DjangoTestSuiteRunner are deprecated; " "use django.test.runner.DiscoverRunner instead.", RemovedInDjango18Warning) 

So redefine DiscoverRunner instead of DjangoTestSuiteRunner .

  from django.test.runner import DiscoverRunner class NoDbTestRunner(DiscoverRunner): """ A test runner to test without database creation/deletion """ def setup_databases(self, **kwargs): pass def teardown_databases(self, old_config, **kwargs): pass 

Use this:

 python manage.py test app --testrunner=app.filename.NoDbTestRunner 
+26
Jul 11 '14 at 13:21
source share

I decided to inherit from django.test.runner.DiscoverRunner and make a couple of additions to the run_tests method.

My first add-on checks if db configuration is needed and allows the normal setup_databases function setup_databases run if db is required. My second addition allows you to run the normal teardown_databases , if the setup_databases method is allowed to run.

My code assumes that any TestCase that inherits from django.test.TransactionTestCase (and therefore django.test.TestCase ) requires a database setup. I made this assumption because the Django docs say:

If you need any other more complex and heavy Django-specific functions, such as ... Testing or using ORM ... then you should use TransactionTestCase or TestCase.

https://docs.djangoproject.com/en/1.6/topics/testing/tools/#django.test.SimpleTestCase

MySite / scripts / settings.py

 from django.test import TransactionTestCase from django.test.runner import DiscoverRunner class MyDiscoverRunner(DiscoverRunner): def run_tests(self, test_labels, extra_tests=None, **kwargs): """ Run the unit tests for all the test labels in the provided list. Test labels should be dotted Python paths to test modules, test classes, or test methods. A list of 'extra' tests may also be provided; these tests will be added to the test suite. If any of the tests in the test suite inherit from ``django.test.TransactionTestCase``, databases will be setup. Otherwise, databases will not be set up. Returns the number of tests that failed. """ self.setup_test_environment() suite = self.build_suite(test_labels, extra_tests) # ----------------- First Addition -------------- need_databases = any(isinstance(test_case, TransactionTestCase) for test_case in suite) old_config = None if need_databases: # --------------- End First Addition ------------ old_config = self.setup_databases() result = self.run_suite(suite) # ----------------- Second Addition ------------- if need_databases: # --------------- End Second Addition ----------- self.teardown_databases(old_config) self.teardown_test_environment() return self.suite_result(suite, result) 

Finally, I added the following line to the settings.py file of the project.

MySite / settings.py

 TEST_RUNNER = 'mysite.scripts.settings.MyDiscoverRunner' 

Now, when running only non-db-dependent tests, my test suite works an order of magnitude faster! :)

+8
Oct. 16 '14 at 4:19
source share

Updated: also see this answer for using a third-party pytest tool.




@ Caesar is right. After randomly running ./manage.py test --settings=no_db_settings without specifying the application name, my development database was destroyed.

For safer use, use the same NoDbTestRunner , but in combination with the following mysite/no_db_settings.py :

 from mysite.settings import * # Test runner with no database creation TEST_RUNNER = 'mysite.scripts.testrunner.NoDbTestRunner' # Use an alternative database as a safeguard against accidents DATABASES['default']['NAME'] = '_test_mysite_db' 

You need to create a database called _test_mysite_db using an external database tool. Then run the following command to create the appropriate tables:

 ./manage.py syncdb --settings=mysite.no_db_settings 

If you are using South, also run the following command:

 ./manage.py migrate --settings=mysite.no_db_settings 

OK!

Now you can run unit tests incredibly fast (and safely) with:

 ./manage.py test myapp --settings=mysite.no_db_settings 
+6
Jun 18 '13 at 16:03
source share

As an alternative to modifying your settings for "NoDbTestRunner" security, a modified version of NoDbTestRunner is used here, which closes the current database connection and removes connection information from the settings and connection object. Work for me, test it in your environment before relying on it :)

 class NoDbTestRunner(DjangoTestSuiteRunner): """ A test runner to test without database creation """ def __init__(self, *args, **kwargs): # hide/disconnect databases to prevent tests that # *do* require a database which accidentally get # run from altering your data from django.db import connections from django.conf import settings connections.databases = settings.DATABASES = {} connections._connections['default'].close() del connections._connections['default'] super(NoDbTestRunner,self).__init__(*args,**kwargs) def setup_databases(self, **kwargs): """ Override the database creation defined in parent class """ pass def teardown_databases(self, old_config, **kwargs): """ Override the database teardown defined in parent class """ pass 
+2
Aug 16 '13 at 18:24
source share

Another solution is to have your test class simply inherit from unittest.TestCase instead of any of the Django test classes. Django docs ( https://docs.djangoproject.com/en/2.0/topics/testing/overview/#writing-tests ) contain the following warning about this:

Using unittest.TestCase avoids the cost of running each test in a transaction and cleaning the database, but if your tests interact with the database, their behavior will depend on the order in which they are run. This can lead to unit tests that pass at startup in isolation but do not work at startup in a bundle.

However, if your test does not use a database, this warning should not concern you, and you can take advantage of not having to run each test case in a transaction.

+2
Feb 21 '18 at 19:36
source share

The above solutions are wonderful too. But the following solution will also reduce db creation time if the number of migrations is more. During unit testing, starting syncdb instead of starting all southern migrations will be much faster.

SOUTH_TESTS_MIGRATE = False # To disable migration and use syncdb instead

0
Jul 14 '14 at 6:00
source share

My web host allows me to create and delete databases from my web GUI, so when I try to run python manage.py test I got the error message "Error creating test database: denial of rights."

I was hoping to use the -keepdb option for django-admin.py, but it seems to be no longer supported with Django 1.7.

What I ended up with is changing the Django code in ... /django/db/backends/creation.py, specifically the _create_test_db and _destroy_test_db functions.

In _create_test_db I commented on the line cursor.execute("CREATE DATABASE ... and replaced it with pass , so the try block will not be empty.

For _destroy_test_db I just commented on cursor.execute("DROP DATABASE - I didn't need to replace anything because there was another command in the block ( time.sleep(1) ).

After that, my tests went fine - although I installed the test version of my regular database separately.

This is not a great solution, because it will be broken if Django is updated, but I had a local copy of Django due to using virtualenv, so at least I have control over when / if I upgrade to more new version.

0
Jan 03 '15 at 22:14
source share

Another solution not mentioned: it was easy to implement, because I already have several settings files (for local / staging / production) that inherit from base.py. Thus, unlike other people, I did not need to overwrite DATABASES ['default'], since DATABASES is not installed in base.py

SimpleTestCase was still trying to connect to my test database and perform the migration. When I created the config / settings / test.py file in which DATABASES did not install anything, my unit tests worked without it. This allowed me to use models with a foreign key and unique constraint fields. (A reverse foreign key lookup that requires a database lookup fails.)

(Django 2.0.6)

PS code snippets

 PROJECT_ROOT_DIR/config/settings/test.py: from .base import * #other test settings #DATABASES = { # 'default': { # 'ENGINE': 'django.db.backends.sqlite3', # 'NAME': 'PROJECT_ROOT_DIR/db.sqlite3', # } #} cli, run from PROJECT_ROOT_DIR: ./manage.py test path.to.app.test --settings config.settings.test path/to/app/test.py: from django.test import SimpleTestCase from .models import * #^assume models.py imports User and defines Classified and UpgradePrice class TestCaseWorkingTest(SimpleTestCase): def test_case_working(self): self.assertTrue(True) def test_models_ok(self): obj = UpgradePrice(title='test',price=1.00) self.assertEqual(obj.title,'test') def test_more_complex_model(self): user = User(username='testuser',email='hi@hey.com') self.assertEqual(user.username,'testuser') def test_foreign_key(self): user = User(username='testuser',email='hi@hey.com') ad = Classified(user=user,headline='headline',body='body') self.assertEqual(ad.user.username,'testuser') #fails with error: def test_reverse_foreign_key(self): user = User(username='testuser',email='hi@hey.com') ad = Classified(user=user,headline='headline',body='body') print(user.classified_set.first()) self.assertTrue(True) #throws exception and never gets here 
0
Jul 11 '18 at 18:37
source share

When using a nose tester (django-nose) you can do something like this:

my_project/lib/nodb_test_runner.py :

 from django_nose import NoseTestSuiteRunner class NoDbTestRunner(NoseTestSuiteRunner): """ A test runner to test without database creation/deletion Used for integration tests """ def setup_databases(self, **kwargs): pass def teardown_databases(self, old_config, **kwargs): pass 

In your settings.py you can specify a test runner there, i.e.

TEST_RUNNER = 'lib.nodb_test_runner.NoDbTestRunner'. # Was 'django_nose.NoseTestSuiteRunner'

OR

I wanted it to run only for specific tests, so I run it like this:

 python manage.py test integration_tests/integration_* --noinput --testrunner=lib.nodb_test_runner.NoDbTestRunner 
0
Feb 06 '19 at 17:37
source share



All Articles