Package: django-capture-on-commit-callbacks
Capture and make assertions ontransaction.on_commit()callbacks.
This allows you to write your tests with theTestCase, rather than needing the slowerTransactionTestCaseto actually commit the transactions.This package was made as a first pass forDjango PR #12944, which is a solution forTicket #30457 “on_commit should be triggered in a TestCase”.
The PR has been merged to Django and has been released in version 3.2, so this package can now be considered a backport.Read more in my blog postThe Fast Way to Test Django transaction.on_commit() Callbacks.InstallationUsepip:python-mpipinstalldjango-capture-on-commit-callbacksRequirementsPython 3.7 to 3.10 supported.Django 3.2 to 4.0 supported.Note:This package is not needed on Django 4.0+ and it will error on usage for such versions.Are your tests slow?Check out my bookSpeed Up Your Django Testswhich covers loads of ways to write faster, more accurate tests.APIcapture_on_commit_callbacks(*,using="default",execute=False)Acts as a context manager that captureson_commitcallbacks for the given database connection.
It returns a list that contains, on exit of the context, the captured callback functions.
From this list you can make assertions on the callbacks or call them to invoke their side effects, emulating a commit.All arguments must be passed as keyword arguments.usingis the alias of the database connection to capture callbacks for.executespecifies whether to call all the callbacks automatically as the context manager exits, if no exception has been raised.For example, you can test a commit hook that sends an email like so:fromdjango.coreimportmailfromdjango.testimportTestCasefromdjango_capture_on_commit_callbacksimportcapture_on_commit_callbacksclassContactTests(TestCase):deftest_post(self):withcapture_on_commit_callbacks()ascallbacks:response=self.client.post("/contact/",{"message":"I like your site"},)self.assertEqual(response.status_code,200)self.assertEqual(len(callbacks),1)# Execute the callbackcallbacks[0]()self.assertEqual(len(mail.outbox),1)self.assertEqual(mail.outbox[0].subject,"Contact Form")self.assertEqual(mail.outbox[0].body,"I like your site")The same test can be written a bit more succinctly withexecute=True:fromdjango.coreimportmailfromdjango.testimportTestCasefromdjango_capture_on_commit_callbacksimportcapture_on_commit_callbacksclassContactTests(TestCase):deftest_post(self):withcapture_on_commit_callbacks(execute=True)ascallbacks:response=self.client.post("/contact/",{"message":"I like your site"},)self.assertEqual(response.status_code,200)self.assertEqual(len(callbacks),1)self.assertEqual(len(mail.outbox),1)self.assertEqual(mail.outbox[0].subject,"Contact Form")self.assertEqual(mail.outbox[0].body,"I like your site")TestCaseMixinA mixin class to be added to your customTestCasesubclass.
It adds one method,captureOnCommitCallbacks()that aliasescapture_on_commit_callbacks(), to match thecamelCasestyle of unittest assertions.You can add to your customTestCaseclasses like so:fromdjangoimporttestfromdjango_capture_on_commit_callbacksimportTestCaseMixinclassTestCase(TestCaseMixin,test.TestCase):passYou could then rewrite the above tests with your customTestCaseclass like so:fromdjango.coreimportmailfromexample.testimportTestCaseclassContactTests(TestCase):deftest_post(self):withself.captureOnCommitCallbacks(execute=True)ascallbacks:response=self.client.post("/contact/",{"message":"I like your site"},)self.assertEqual(response.status_code,200)self.assertEqual(len(callbacks),1)self.assertEqual(len(mail.outbox),1)self.assertEqual(mail.outbox[0].subject,"Contact Form")self.assertEqual(mail.outbox[0].body,"I like your site")