diff --git a/django/test/testcases.py b/django/test/testcases.py index b947d70a45..41d01b89b4 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -1282,11 +1282,18 @@ class TestCase(TransactionTestCase): try: yield callbacks finally: - run_on_commit = connections[using].run_on_commit[start_count:] - callbacks[:] = [func for sids, func in run_on_commit] - if execute: - for callback in callbacks: - callback() + callback_count = len(connections[using].run_on_commit) + while True: + run_on_commit = connections[using].run_on_commit[start_count:] + callbacks[:] = [func for sids, func in run_on_commit] + if execute: + for callback in callbacks: + callback() + + if callback_count == len(connections[using].run_on_commit): + break + start_count = callback_count - 1 + callback_count = len(connections[using].run_on_commit) class CheckCondition: diff --git a/docs/releases/4.0.txt b/docs/releases/4.0.txt index 45743c4139..53b738c8ec 100644 --- a/docs/releases/4.0.txt +++ b/docs/releases/4.0.txt @@ -368,6 +368,9 @@ Tests * The :option:`test --parallel` option now supports the value ``auto`` to run one test process for each processor core. +* :meth:`.TestCase.captureOnCommitCallbacks` now captures new callbacks added + while executing :func:`.transaction.on_commit` callbacks. + URLs ~~~~ diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt index 8e4f077fad..846d428980 100644 --- a/docs/topics/testing/tools.txt +++ b/docs/topics/testing/tools.txt @@ -912,6 +912,11 @@ It also provides an additional method: self.assertEqual(mail.outbox[0].subject, 'Contact Form') self.assertEqual(mail.outbox[0].body, 'I like your site') + .. versionchanged:: 4.0 + + In older versions, new callbacks added while executing + :func:`.transaction.on_commit` callbacks were not captured. + .. _live-test-server: ``LiveServerTestCase`` diff --git a/tests/test_utils/tests.py b/tests/test_utils/tests.py index 44301aaa1b..82ec36ab38 100644 --- a/tests/test_utils/tests.py +++ b/tests/test_utils/tests.py @@ -1502,6 +1502,13 @@ class CaptureOnCommitCallbacksTests(TestCase): self.assertEqual(callbacks, []) + def test_execute_recursive(self): + with self.captureOnCommitCallbacks(execute=True) as callbacks: + transaction.on_commit(self.enqueue_callback) + + self.assertEqual(len(callbacks), 2) + self.assertIs(self.callback_called, True) + class DisallowedDatabaseQueriesTests(SimpleTestCase): def test_disallowed_database_connections(self):