Django Custom Test Runner
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 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’spytest.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-db
command 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:
Enable database access. Should be followed by a call torestore()
.
Disable database access. Should be followed by a call torestore()
.
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 db
fixture, 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 db
fixture. 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