Skip to content

Commit 7473c84

Browse files
meshysamueljsb
andcommitted
Control after-commit calls in tests with a setting
By default, in (non transaction testcase) tests, we simulate the execution of after-commit callbacks, to reproduce Django's normal behaviour when outside of tests. To allow projects to migrate their existing code over to this new paradigm gradually, we allow this behaviour to be disabled. This was previously controlled by monkey-patching a module scoped value in tests, but is now exposed with a Django setting: `SUBATOMIC_RUN_AFTER_COMMIT_CALLBACKS_IN_TESTS`. Co-authored-by: Samuel Searles-Bryant <sam.searles-bryant@kraken.tech>
1 parent 93c4785 commit 7473c84

File tree

3 files changed

+21
-45
lines changed

3 files changed

+21
-45
lines changed

pytest.ini

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,6 @@ addopts =
4545
# https://pytest-django.readthedocs.io/
4646
-p django
4747

48-
markers =
49-
# See Note [Running after-commit callbacks in tests]
50-
deprecated_ignore_after_commit_callbacks: require after-commit callbacks to be explicitly captured and called
51-
5248
# Ensure passing xfailed (aka "xpass") tests cause the test suite to fail.
5349
# https://docs.pytest.org/en/latest/skipping.html#strict-parameter
5450
xfail_strict = true

src/django_subatomic/db.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,6 @@
1515
from typing import NoReturn
1616

1717

18-
# See Note [Running after-commit callbacks in tests]
19-
_RUN_AFTER_COMMIT_CALLBACKS_IN_TESTS = True
20-
21-
2218
def dbs_with_open_transactions() -> frozenset[str]:
2319
"""
2420
Get the names of databases with open transactions.
@@ -224,7 +220,8 @@ def _execute_on_commit_callbacks_in_tests(using: str | None = None) -> Iterator[
224220
"""
225221
yield
226222
if (
227-
_RUN_AFTER_COMMIT_CALLBACKS_IN_TESTS # See Note [Running after-commit callbacks in tests]
223+
# See Note [Running after-commit callbacks in tests]
224+
getattr(settings, "SUBATOMIC_RUN_AFTER_COMMIT_CALLBACKS_IN_TESTS", True)
228225
and _innermost_atomic_block_wraps_testcase(using=using)
229226
):
230227
connection = django_transaction.get_connection(using)
@@ -335,6 +332,9 @@ class _UnexpectedDanglingTransaction(Exception):
335332
# To avoid that problem, we capture after-commit callbacks and execute them when we exit the
336333
# outermost `transaction` or `transaction_if_not_already` context. This emulates how the application
337334
# will behave when deployed and means that our tests are testing realistic application behaviour.
335+
#
336+
# To help projects migrate to this behaviour, it can be disabled with
337+
# the Django setting `SUBATOMIC_RUN_AFTER_COMMIT_CALLBACKS_IN_TESTS`.
338338

339339

340340
def run_after_commit(
@@ -368,8 +368,11 @@ def run_after_commit(
368368
if needs_transaction and not in_transaction(using=using):
369369
raise _MissingRequiredTransaction(database=using)
370370

371-
# See Note [Running after-commit callbacks in tests]
372-
if _RUN_AFTER_COMMIT_CALLBACKS_IN_TESTS and only_in_testcase_transaction:
371+
if (
372+
# See Note [Running after-commit callbacks in tests]
373+
getattr(settings, "SUBATOMIC_RUN_AFTER_COMMIT_CALLBACKS_IN_TESTS", True)
374+
and only_in_testcase_transaction
375+
):
373376
callback()
374377
else:
375378
django_transaction.on_commit(callback, using=using)

tests/test_db.py

Lines changed: 11 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import re
44
from typing import TYPE_CHECKING
5-
from unittest import mock
65

76
import pytest
87
from django import db as django_db
@@ -14,7 +13,7 @@
1413

1514
if TYPE_CHECKING:
1615
import contextlib
17-
from collections.abc import Callable, Generator
16+
from collections.abc import Callable
1817
from typing import Protocol
1918

2019
import pytest_django
@@ -60,28 +59,6 @@ def _parametrize_transaction_testcase(func: Callable[..., None]) -> MarkDecorato
6059
return parametrize(usefixtures(func)) # type: ignore[no-any-return]
6160

6261

63-
@pytest.fixture(autouse=True)
64-
def _prevent_after_commit_callbacks(
65-
request: pytest.FixtureRequest,
66-
) -> Generator[None, None, None]:
67-
"""
68-
Disable automatically running after-commit callbacks in marked tests.
69-
70-
If a test relies on after-commit callbacks never being called, it may be
71-
marked with `deprecated_ignore_after_commit_callbacks` to disable test simulation
72-
of how after-commit callbacks are naturally run after transactions.
73-
74-
TODO: Allow users of this library to do this in their own tests.
75-
76-
See Note [Running after-commit callbacks in tests]
77-
"""
78-
if "deprecated_ignore_after_commit_callbacks" in request.keywords:
79-
with mock.patch.object(db, "_RUN_AFTER_COMMIT_CALLBACKS_IN_TESTS", new=False):
80-
yield
81-
else:
82-
yield
83-
84-
8562
class Counter:
8663
def __init__(self) -> None:
8764
self.count = 0
@@ -463,25 +440,24 @@ def test_not_executed_if_rolled_back(self) -> None:
463440
assert counter.count == 0
464441

465442

466-
class TestRunAfterCommitDeprecatedTestBehaviour:
467-
@pytest.mark.deprecated_ignore_after_commit_callbacks
443+
class TestRunAfterCommitCallbacksSettingBehaviour:
468444
@override_settings(SUBATOMIC_AFTER_COMMIT_NEEDS_TRANSACTION=False)
469445
def test_does_not_execute_when_in_testcase_transaction_if_callbacks_disabled(
470446
self,
471447
) -> None:
472448
"""
473-
`run_after_commit` should not ignore testcase transactions when test marked with `deprecated_ignore_after_commit_callbacks`.
449+
`run_after_commit` should not ignore testcase transactions when SUBATOMIC_RUN_AFTER_COMMIT_CALLBACKS_IN_TESTS is False.
474450
475451
We have to disable `SUBATOMIC_AFTER_COMMIT_NEEDS_TRANSACTION`, because
476452
otherwise an error would be raised when trying to register the callback.
477453
"""
478454
counter = Counter()
479455

480-
db.run_after_commit(counter.increment)
456+
with override_settings(SUBATOMIC_RUN_AFTER_COMMIT_CALLBACKS_IN_TESTS=False):
457+
db.run_after_commit(counter.increment)
481458

482459
assert counter.count == 0
483460

484-
@pytest.mark.deprecated_ignore_after_commit_callbacks
485461
@pytest.mark.parametrize(
486462
"transaction_context",
487463
(
@@ -493,16 +469,17 @@ def test_does_not_execute_after_commit_if_decorated(
493469
self, transaction_context: DBContextManager
494470
) -> None:
495471
"""
496-
`run_after_commit` should not execute the callback when test marked with `deprecated_ignore_after_commit_callbacks`.
472+
`run_after_commit` should not execute the callback when SUBATOMIC_RUN_AFTER_COMMIT_CALLBACKS_IN_TESTS is False.
497473
"""
498474
counter = Counter()
499475

500-
with transaction_context():
501-
db.run_after_commit(counter.increment)
476+
with override_settings(SUBATOMIC_RUN_AFTER_COMMIT_CALLBACKS_IN_TESTS=False):
477+
with transaction_context():
478+
db.run_after_commit(counter.increment)
502479

503-
assert counter.count == 0
480+
assert counter.count == 0
504481

505-
assert counter.count == 0
482+
assert counter.count == 0
506483

507484

508485
class TestInTransaction:

0 commit comments

Comments
 (0)