[Fixed]-How to test a Django on_commit hook without clearing the database?

9👍

Starting with version 3.2 Django has a build-in way to test the on_comit hook.
Example:

from django.core import mail
from django.test import TestCase


class ContactTests(TestCase):
    def test_post(self):
        with self.captureOnCommitCallbacks(execute=True) as callbacks:
            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')

Here is the official documentation: https://docs.djangoproject.com/en/stable/topics/testing/tools/#django.test.TestCase.captureOnCommitCallbacks

👤Slava

10👍

Just keep using TestCase and fake commit forcing executing of posponed actions in run_and_clear_commit_hooks.
Check this article:

https://medium.com/gitux/speed-up-django-transaction-hooks-tests-6de4a558ef96

1👍

Adam Johnson wrote this, and I think the code referenced here does the trick:

https://adamj.eu/tech/2020/05/20/the-fast-way-to-test-django-transaction-on-commit-callbacks/

@contextmanager
    def captureOnCommitCallbacks(cls, *, using=DEFAULT_DB_ALIAS, execute=False):
        """Context manager to capture transaction.on_commit() callbacks."""
        callbacks = []
        start_count = len(connections[using].run_on_commit)
        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()

usage:

class ContactTests(TestCase):
            def test_post(self):
                with self.captureOnCommitCallbacks(execute=True) as callbacks:
                    response = self.client.post(
                        '/contact/',
                        {'message': 'I like your site'},
                    )

                self.assertEqual(response.status_code, 200)
                self.assertEqual(len(callbacks), 1)

0👍

I have two possibilities in mind:

  1. as this section says post_migrate emitted after flush, so you can perform preloading some useful data
  2. You can subclass TransactionTestCase and implement your _fixture_teardown (you can see that flush is called there in the very end of method).

I’d probably stick with first one if your migration isn’t too expensive and with second one if it is.

Leave a comment