Skip to content

[MINOR] add opt-in multi-database support for db fixtures #896

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 23 additions & 11 deletions docs/database.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,22 +66,34 @@ select using an argument to the ``django_db`` mark::
Tests requiring multiple databases
----------------------------------

Currently ``pytest-django`` does not specifically support Django's
multi-database support.
You can configure ``pytest-django`` to create transactions (or cleanup) databases
other than ``default`` by adding a special key to your database test configuration
in your django settings.

for example::

DATABASES = {
"default": {
# ... normal default settings
},
"secondary": {
# ... engine settings and so forth
"TEST": {
"PYTEST_DJANGO_ALLOW_TRANSACTIONS": True
}
}
}


With that database configuration in your test environment, tests that depend on the
``db`` or ``transactional_db`` fixtures can make changes in the "secondary" database
and have it cleaned up after each test.

You can however use normal :class:`~django.test.TestCase` instances to use its
You could also use normal :class:`~django.test.TestCase` instances to use its
:ref:`django:topics-testing-advanced-multidb` support.
In particular, if your database is configured for replication, be sure to read
about :ref:`django:topics-testing-primaryreplica`.

If you have any ideas about the best API to support multiple databases
directly in ``pytest-django`` please get in touch, we are interested
in eventually supporting this but unsure about simply following
Django's approach.

See `pull request 431 <https://github.com/pytest-dev/pytest-django/pull/431>`_
for an idea/discussion to approach this.

``--reuse-db`` - reuse the testing database between test runs
--------------------------------------------------------------
Using ``--reuse-db`` will create the test database in the same way as
Expand Down
40 changes: 33 additions & 7 deletions pytest_django/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def teardown_database():


def _django_db_fixture_helper(
request, django_db_blocker, transactional=False, reset_sequences=False
request, django_db_blocker, settings, transactional=False, reset_sequences=False,
):
if is_django_unittest(request):
return
Expand All @@ -148,11 +148,37 @@ class ResetSequenceTestCase(django_case):
else:
from django.test import TestCase as django_case

# specify attributes on test cases for multi-database support
# https://docs.djangoproject.com/en/3.1/topics/testing/tools/#multi-database-support
transactional_databases = _transactional_databases(settings)
if transactional_databases:

class MultiDatabaseTransactionTestCase(django_case):
databases = transactional_databases

django_case = MultiDatabaseTransactionTestCase
request.config.django_transactional_databases = transactional_databases

test_case = django_case(methodName="__init__")
test_case._pre_setup()
request.addfinalizer(test_case._post_teardown)


def _transactional_databases(settings):
"""
Get database labels from settings in which pytest-django should start a transaction.
"""
transactional_databases = {"default"}
for label, config in settings.DATABASES.items():
if label == "default":
continue

if config.get("TEST", {}).get("PYTEST_DJANGO_ALLOW_TRANSACTIONS"):
transactional_databases.add(label)

return transactional_databases


def _disable_native_migrations():
from django.conf import settings
from django.core.management.commands import migrate
Expand Down Expand Up @@ -196,7 +222,7 @@ def _set_suffix_to_test_databases(suffix):


@pytest.fixture(scope="function")
def db(request, django_db_setup, django_db_blocker):
def db(request, django_db_setup, django_db_blocker, settings):
"""Require a django test database.

This database will be setup with the default fixtures and will have
Expand All @@ -218,11 +244,11 @@ def db(request, django_db_setup, django_db_blocker):
):
request.getfixturevalue("transactional_db")
else:
_django_db_fixture_helper(request, django_db_blocker, transactional=False)
_django_db_fixture_helper(request, django_db_blocker, settings, transactional=False)


@pytest.fixture(scope="function")
def transactional_db(request, django_db_setup, django_db_blocker):
def transactional_db(request, django_db_setup, django_db_blocker, settings):
"""Require a django test database with transaction support.

This will re-initialise the django database for each test and is
Expand All @@ -237,11 +263,11 @@ def transactional_db(request, django_db_setup, django_db_blocker):
"""
if "django_db_reset_sequences" in request.fixturenames:
request.getfixturevalue("django_db_reset_sequences")
_django_db_fixture_helper(request, django_db_blocker, transactional=True)
_django_db_fixture_helper(request, django_db_blocker, settings, transactional=True)


@pytest.fixture(scope="function")
def django_db_reset_sequences(request, django_db_setup, django_db_blocker):
def django_db_reset_sequences(request, django_db_setup, django_db_blocker, settings):
"""Require a transactional test database with sequence reset support.

This behaves like the ``transactional_db`` fixture, with the addition
Expand All @@ -254,7 +280,7 @@ def django_db_reset_sequences(request, django_db_setup, django_db_blocker):
``transactional_db``, ``django_db_reset_sequences``.
"""
_django_db_fixture_helper(
request, django_db_blocker, transactional=True, reset_sequences=True
request, django_db_blocker, settings, transactional=True, reset_sequences=True
)


Expand Down
2 changes: 1 addition & 1 deletion pytest_django_test/settings_sqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": "/should_not_be_accessed",
}
},
}
7 changes: 7 additions & 0 deletions pytest_django_test/settings_sqlite_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,12 @@
"ENGINE": "django.db.backends.sqlite3",
"NAME": "/pytest_django_should_never_get_accessed",
"TEST": {"NAME": _filename},
},
"secondary": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": "/should_not_be_accessed_two",
"TEST": {
"PYTEST_DJANGO_ALLOW_TRANSACTIONS": True
}
}
}
22 changes: 21 additions & 1 deletion tests/test_database.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import pytest
from django.db import connection
from django.db import connection, connections
from django.test.testcases import connections_support_transactions

from pytest_django_test.app.models import Item
Expand Down Expand Up @@ -138,6 +138,26 @@ def test_fin(self, fin):
# Check finalizer has db access (teardown will fail if not)
pass

def test_db_sets_transactions_in_all_configured_connections(self, settings, db):
assert all(
conn.in_atomic_block
for conn in connections.all() if conn.features.supports_transactions
)

def test_multi_database_true_if_settings_permit_db(self, request, settings, transactional_db):
transactional_databases = set()
for label, config in settings.DATABASES.items():
if config.get("TEST", {}).get("PYTEST_DJANGO_ALLOW_TRANSACTIONS"):
transactional_databases.add(label)

if not transactional_databases:
pytest.skip(
"settings.DATABASES is not configured to create transactions"
"in databases outside of default"
)

assert {"default", "secondary"} == request.config.django_transactional_databases


class TestDatabaseFixturesAllOrder:
@pytest.fixture
Expand Down