Skip to content

Commit cb4a5e9

Browse files
authored
Don't run _pre_setup() twice for transactional tests (#1293)
* Add test for repeated _pre_setup on transactional tests Records who calls _pre_setup(). A plain django_db test is set up once, by us. A django_db(transaction=True) test is set up twice: TransactionTestCase.setUpClass() runs _pre_setup() eagerly and we then run it again. * Don't run _pre_setup() twice for transactional tests TransactionTestCase.setUpClass() runs _pre_setup() eagerly and flags it in _pre_setup_ran_eagerly, which unittest.TestCase.run() clears instead of setting the test up again. We drive the test case ourselves, so do the same. Repeating _pre_setup() redid the whole fixture setup - serialized rollback restore, fixtures, sequence reset - on every transactional test. With available_apps it also sent setting_changed(enter=True) and post_migrate twice against the single exit in _post_teardown.
1 parent 9d6570a commit cb4a5e9

3 files changed

Lines changed: 45 additions & 1 deletion

File tree

docs/changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Bugfixes
1414

1515
* Fixed type hints of assert methods to match actual signature (`PR #1271 <https://github.com/pytest-dev/pytest-django/pull/1271>`__)
1616
* Handled Django 6.2's ``ImproperlyConfigured`` (in addition to ``ImportError``) when the configured ``DJANGO_SETTINGS_MODULE`` cannot be imported, so pytest-django still shows its guidance message.
17+
* Fixed ``django_db(transaction=True)`` tests being set up twice, which repeated the ``serialized_rollback`` restore and the ``fixtures`` load, and sent ``setting_changed`` and ``post_migrate`` twice when ``available_apps`` is set.
1718

1819
v4.12.0 (2026-02-14)
1920
--------------------

pytest_django/fixtures.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,10 @@ def tearDownClass(cls) -> None:
289289
PytestDjangoTestCase.setUpClass()
290290

291291
test_case = PytestDjangoTestCase(methodName="__init__")
292-
test_case._pre_setup()
292+
if not PytestDjangoTestCase._pre_setup_ran_eagerly:
293+
# For a TransactionTestCase, setUpClass() has already run _pre_setup() and set
294+
# this flag to say so.
295+
test_case._pre_setup()
293296

294297
yield
295298

tests/test_database.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,46 @@ def test_db_access(self):
463463
result.assert_outcomes(passed=1)
464464

465465

466+
def test_pre_setup_runs_once_per_test(django_pytester: DjangoPytester) -> None:
467+
"""Each test is set up by exactly one _pre_setup() call.
468+
469+
A plain test is set up by us, a transactional one by Django's
470+
TransactionTestCase.setUpClass(), which runs _pre_setup() eagerly.
471+
"""
472+
473+
django_pytester.create_test_module(
474+
"""
475+
import sys
476+
477+
import pytest
478+
from django.test import TransactionTestCase
479+
480+
# Name of the function that called _pre_setup(), appended once per call and
481+
# never reset, so the list below is the setup history of the whole module.
482+
callers = []
483+
original_pre_setup = TransactionTestCase._pre_setup.__func__
484+
485+
@classmethod
486+
def recording_pre_setup(cls):
487+
callers.append(sys._getframe(1).f_code.co_name)
488+
original_pre_setup(cls)
489+
490+
TransactionTestCase._pre_setup = recording_pre_setup
491+
492+
@pytest.mark.django_db
493+
def test_plain_db():
494+
assert callers == ["_django_db_helper"]
495+
496+
@pytest.mark.django_db(transaction=True)
497+
def test_transactional_db():
498+
assert callers == ["_django_db_helper", "setUpClass"]
499+
"""
500+
)
501+
502+
result = django_pytester.runpytest_subprocess("-v", "--reuse-db")
503+
result.assert_outcomes(passed=2)
504+
505+
466506
class Test_database_blocking:
467507
def test_db_access_in_conftest(self, django_pytester: DjangoPytester) -> None:
468508
"""Make sure database access in conftest module is prohibited."""

0 commit comments

Comments
 (0)