Django Custom Test Runner

  1. Django Test Client
  2. Django Custom Test Runner Reviews

The best base class for most tests is django.test.TestCase. This test class creates a clean database before its tests are run, and runs every test function in its own transaction. The class also owns a test Client that you can use to simulate a user interacting with the code at the view level.

pytest-django takes a conservative approach to enabling databaseaccess. By default your tests will fail if they try to access thedatabase. Only if you explicitly request database access will this beallowed. This encourages you to keep database-needing tests to aminimum which makes it very clear what code uses the database.

  • Django’s entire test suite takes a while to run, and running every single test could be redundant if, say, you just added a test to Django that you want to run quickly without running everything else. You can run a subset of the unit tests by appending the names of the test modules to runtests.py on the command line.
  • Creating a Custom Middleware. To ilustrated this post, let’s create a custom Middleware class that intercept all the exceptions that occur in our view functions then grab the exception message and query the StackOverflow API and return the three top answers and print it to the terminal.

Enabling database access in tests

You can use pytest marks to tell pytest-django yourtest needs database access:

It is also possible to mark all tests in a class or module at once.This demonstrates all the ways of marking, even though they overlap.Just one of these marks would have been sufficient. See the pytestdocumentation for detail:

By default pytest-django will set up the Django databases thefirst time a test needs them. Once setup, the database is cached to beused for all subsequent tests and rolls back transactions, to isolatetests from each other. This is the same way the standard DjangoTestCase uses the database. Howeverpytest-django also caters for transaction test cases and allowsyou to keep the test databases configured across different test runs.

Testing transactions

Django Custom Test RunnerDjango Custom Test Runner

Django itself has the TransactionTestCase whichallows you to test transactions and will flush the database betweentests to isolate them. The downside of this is that these tests aremuch slower to set up due to the required flushing of the database.pytest-django also supports this style of tests, which you canselect using an argument to the django_db mark:

Tests requiring multiple databases

New in version 4.3.

Caution

This support is experimental and is subject to change withoutdeprecation. We are still figuring out the best way to expose thisfunctionality. If you are using this successfully or unsuccessfully,let us know!

pytest-django has experimental support for multi-database configurations.Currently pytest-django does not specifically support Django’smulti-database support, using the databases argument to thedjango_db mark:

For details see django.test.TransactionTestCase.databases anddjango.test.TestCase.databases.

--reuse-db - reuse the testing database between test runs

Using --reuse-db will create the test database in the same way asmanage.pytest usually does.

However, after the test run, the test database will not be removed.

The next time a test run is started with --reuse-db, the database willinstantly be re used. This will allow much faster startup time for tests.

This can be especially useful when running a few tests, when there are a lotof database tables to set up.

--reuse-db will not pick up schema changes between test runs. You must runthe tests with --reuse-db--create-db to re-create the database accordingto the new schema. Running without --reuse-db is also possible, since thedatabase will automatically be re-created.

Django Test Client

--create-db - force re creation of the test database

When used with --reuse-db, this option will re-create the database,regardless of whether it exists or not.

Example work flow with --reuse-db and --create-db.

A good way to use --reuse-db and --create-db can be:

  • Put --reuse-db in your default options (in your project’s pytest.ini file):

  • Just run tests with pytest, on the first run the test database will becreated. The next test run it will be reused.

  • When you alter your database schema, run pytest--create-db, to forcere-creation of the test database.

--no-migrations - Disable Django migrations

Using --no-migrations (alias: --nomigrations) will disable Django migrations and create the databaseby inspecting all models. It may be faster when there are several migrations torun in the database setup. You can use --migrations to force runningmigrations in case --no-migrations is used, e.g. in setup.cfg.

Advanced database configuration

pytest-django provides options to customize the way database is configured. Thedefault database construction mostly follows Django’s own test runner. You canhowever influence all parts of the database setup process to make it fit inprojects with special requirements.

This section assumes some familiarity with the Django test runner, Djangodatabase creation and pytest fixtures.

Fixtures

There are some fixtures which will let you change the way the database isconfigured in your own project. These fixtures can be overridden in your ownproject by specifying a fixture with the same name and scope in conftest.py.

Use the pytest-django source code

The default implementation of these fixtures can be found infixtures.py.

The code is relatively short and straightforward and can provide astarting point when you need to customize database setup in your ownproject.

django_db_setup

This is the top-level fixture that ensures that the test databases are createdand available. This fixture is session scoped (it will be run once per testsession) and is responsible for making sure the test database is available for teststhat need it.

The default implementation creates the test database by applying migrations and removesdatabases after the test run.

You can override this fixture in your own conftest.py to customize how testdatabases are constructed.

django_db_modify_db_settings

This fixture allows modifyingdjango.conf.settings.DATABASESjust before the databases are configured.

If you need to customize the location of your test database, this is thefixture you want to override.

The default implementation of this fixture requests thedjango_db_modify_db_settings_parallel_suffix to provide compatibilitywith pytest-xdist.

This fixture is by default requested from django_db_setup.

Django Custom Test Runner Reviews

django_db_modify_db_settings_parallel_suffix

Requesting this fixture will add a suffix to the database name when the testsare run via pytest-xdist, or via tox in parallel mode.

This fixture is by default requested fromdjango_db_modify_db_settings.

django_db_modify_db_settings_tox_suffix

Requesting this fixture will add a suffix to the database name when the testsare run via tox in parallel mode.

This fixture is by default requested fromdjango_db_modify_db_settings_parallel_suffix.

django_db_modify_db_settings_xdist_suffix

Requesting this fixture will add a suffix to the database name when the testsare run via pytest-xdist.

This fixture is by default requested fromdjango_db_modify_db_settings_parallel_suffix.

django_db_use_migrations

Returns whether or not to use migrations to create the testdatabases.

The default implementation returns the value of the--migrations/--no-migrations command line options.

This fixture is by default requested from django_db_setup.

django_db_keepdb

Returns whether or not to re-use an existing database and to keep it after thetest run.

The default implementation handles the --reuse-db and --create-dbcommand line options.

This fixture is by default requested from django_db_setup.

django_db_createdb

Returns whether or not the database is to be re-created before running anytests.

This fixture is by default requested from django_db_setup.

django_db_blocker

Warning

It does not manage transactions and changes made to the database will notbe automatically restored. Using the pytest.mark.django_db markeror db fixture, which wraps database changes in a transaction andrestores the state is generally the thing you want in tests. This markercan be used when you are trying to influence the way the database isconfigured.

Database access is by default not allowed. django_db_blocker is the objectwhich can allow specific code paths to have access to the database. Thisfixture is used internally to implement the db fixture.

django_db_blocker can be used as a context manager to enable databaseaccess for the specified block:

You can also manage the access manually via these methods:

django_db_blocker.unblock()

Enable database access. Should be followed by a call torestore().

django_db_blocker.block()

Disable database access. Should be followed by a call torestore().

django_db_blocker.restore()

Restore the previous state of the database blocking.

Examples

Using a template database for tests

This example shows how a pre-created PostgreSQL source database can be copiedand used for tests.

Put this into conftest.py:

Using an existing, external database for tests

This example shows how you can connect to an existing database and use it foryour tests. This example is trivial, you just need to disable all ofpytest-django and Django’s test database creation and point to the existingdatabase. This is achieved by simply implementing a no-opdjango_db_setup fixture.

Put this into conftest.py:

Populate the database with initial test data

In some cases you want to populate the test database before you start thetests. Because of different ways you may use the test database, there aredifferent ways to populate it.

Populate the test database if you don’t use transactional or live_server

If you are using the pytest.mark.django_db() marker or dbfixture, you probably don’t want to explicitly handle transactions in yourtests. In this case, it is sufficient to populate your database onlyonce. You can put code like this in conftest.py:

This loads the Django fixture my_fixture.json once for the entire testsession. This data will be available to tests marked with thepytest.mark.django_db() mark, or tests which use the dbfixture. The test data will be saved in the database and will not be reset.This example uses Django’s fixture loading mechanism, but it can be replacedwith any way of loading data into the database.

Notice django_db_setup in the argument list. This triggers theoriginal pytest-django fixture to create the test database, so that whencall_command is invoked, the test database is already prepared andconfigured.

Populate the test database if you use transactional or live_server

In case you use transactional tests (you use the pytest.mark.django_db()marker with transaction=True, or the transactional_db fixture),you need to repopulate your database every time a test starts, because thedatabase is cleared between tests.

The live_server fixture uses transactional_db, so youalso need to populate the test database this way when using it.

You can put this code into conftest.py. Note that while it it is similar tothe previous one, the scope is changed from session to function:

Use the same database for all xdist processes

By default, each xdist process gets its own database to run tests on. This isneeded to have transactional tests that do not interfere with each other.

If you instead want your tests to use the same database, override thedjango_db_modify_db_settings to not do anything. Put this inconftest.py:

Randomize database sequences

You can customize the test database after it has been created by extending thedjango_db_setup fixture. This example shows how to give a PostgreSQLsequence a random starting value. This can be used to detect and preventprimary key id’s from being hard-coded in tests.

Put this in conftest.py:

Create the test database from a custom SQL script

You can replace the django_db_setup fixture and run any code in itsplace. This includes creating your database by hand by running a SQL scriptdirectly. This example shows sqlite3’s executescript method. In a moregeneral use case, you probably want to load the SQL statements from a file orinvoke the psql or the mysql command line tool.

Put this in conftest.py:

Warning

This snippet shows cursor().executescript() which is sqlite specific, forother database engines this method might differ. For instance, psycopg2 usescursor().execute().

Use a read only database

You can replace the ordinary django_db_setup to completely avoid databasecreation/migrations. If you have no need for rollbacks or truncating tables,you can simply avoid blocking the database and use it directly. When using thismethod you must ensure that your tests do not change the database state.

Put this in conftest.py:

Running the tests¶

Run these tests from the project dts_test_project, it comes prepacked with the correct settings file and extra apps to enable tests to ensure different apps can exist in SHARED_APPS and TENANT_APPS.

If you want to run with custom migration executor then do

You can also run the tests with docker-compose

Updating your app’s tests to work with django_tenants¶

Because django will not create tenants for you during your tests, we have packed some custom test cases and other utilities. If you want a test to happen at any of the tenant’s domain, you can use the test case TenantTestCase. It will automatically create a tenant for you, set the connection’s schema to tenant’s schema and make it available at self.tenant. We have also included a TenantRequestFactory and a TenantClient so that your requests will all take place at the tenant’s domain automatically. Here’s an example

Additional information¶

You may have other fields on your tenant or domain model which are required fields.If you have there are two routines to look at setup_tenant and setup_domain

You can also change the test domain name and the test schema name by using get_test_schema_name and get_test_tenant_domain.by default the domain name is tenant.test.com and the schema name is test.

You can set the the verbosity by overriding the get_verbosity method.

Running tests faster¶

Using the TenantTestCase can make running your tests really slow quite early in your project. This is due to the fact that it drops, recreates the test schema and runs migrations for every TenantTestCase you have. If you want to gain speed, there’s a FastTenantTestCase where the test schema will be created and migrations ran only one time. The gain in speed is noticiable but be aware that by using this you will be perpertraiting state between your test cases, please make sure your they wont be affected by this.

Running tests using TenantTestCase can start being a bottleneck once the number of tests grow. If you do not care that the state between tests is kept, an alternative is to use the class FastTenantTestCase. Unlike TenantTestCase, the test schema and its migrations will only be created and ran once. This is a significant improvement in speed coming at the cost of shared state.

There are some extra method that you can use for FastTenantTestCase. They are.

flush_data default is True which means is will empty the table after each run. False will keep the data

use_existing_tenant Gets run if the setup doesn’t need to create a new databaseuse_new_tenant Get run is an new database is created