Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

django unit tests without a db

Tags:

django

testing

People also ask

Should unit tests require a database?

Unit tests shouldn't depend on infrastructureThere's no way to test this function without having a database connection available at the time of testing. If a new developer clones the project they will need to set up a database or else tests will fail.

What database does Django test use?

Writing tests Django's unit tests use a Python standard library module: unittest .

Does Django use Pytest or unittest?

Helps you write better programs. Many developers from Python community heard of and used unit testing to test their projects and knew about boilerplate code with Python and Django unittest module. But Pytest suggests much more pythonic tests without boilerplate.


You can subclass DjangoTestSuiteRunner and override setup_databases and teardown_databases methods to pass.

Create a new settings file and set TEST_RUNNER to the new class you just created. Then when you're running your test, specify your new settings file with --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 a custom settings:

from mysite.settings import *

# Test runner with no database creation
TEST_RUNNER = 'mysite.scripts.testrunner.NoDbTestRunner'

When you're running your tests, run it like the following with --settings flag set to your new settings file:

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

UPDATE: April/2018

Since Django 1.8, the module django.test.simple.DjangoTestSuiteRunner were moved to 'django.test.runner.DiscoverRunner'.

For more info check official doc section about custom test runners.


Generally tests in an application can be classified in to two categories

  1. Unit tests, these test the individual snippets of code in insolation and do not require to go to the database
  2. Integration test cases which actually go to the database and test the fully integrated logic.

Django supports both unit and integration tests.

Unit tests, do not require to setup and tear down database and these we should inherit from SimpleTestCase.

from django.test import SimpleTestCase


class ExampleUnitTest(SimpleTestCase):
    def test_something_works(self):
        self.assertTrue(True)

For integration test cases inherit from TestCase in turn inherits from TransactionTestCase and it will setup and tear down 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 will ensure that database in created and destroyed only for the test cases that access the database and therefore tests will be more efficient


From django.test.simple

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

So override 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 like that :

python manage.py test app --testrunner=app.filename.NoDbTestRunner

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

My first addition checks to see if setting up a db is necessary and allows the normal setup_databases functionality to kick in if a db is necessary. My second addition allows the normal teardown_databases to run if the setup_databases method was allowed to run.

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

If you need any of the other more complex and heavyweight Django-specific features like ... Testing or using the ORM ... then you should use TransactionTestCase or TestCase instead.

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 my project's settings.py file.

mysite/settings.py

TEST_RUNNER = 'mysite.scripts.settings.MyDiscoverRunner'

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


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


@Cesar is right. After accidentally running ./manage.py test --settings=no_db_settings, without specifying an app name, my development database was wiped out.

For a safer manner, use the same NoDbTestRunner, but in conjunction 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 corresponding tables:

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

If you're using South, also run the following command:

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

OK!

You can now run unit tests blazingly fast (and safe) by:

./manage.py test myapp --settings=mysite.no_db_settings