Speed up Django transaction hooks tests


django-transactions-hooks was merged to Django core in 1.9 release. It is a robust solution for performing actions only after your database commits, queuing tasks, sending emails, posting data to external services, …

Avoid TransactionTestCase (SnailTestCase)

If your tests are based on TestCase no commit is ever done, you will be forced to adopt TransactionTestCase. Your tests execution time will raise dramatically. Remember what TransactionTestCase does:

  • resets DB after every test execution
  • fixtures are loaded for every test execution
  • Can’t use SetUpTestData to load shared data once per test class

It is a high price and it’s reasonable to have the temptation to leave your tests without checking that delayed code portion. Or maybe you are a bullheaded tester and have tattooed duck typing and mocking in your head.

“If it walks like a duck and it quacks like a duck, then it must be a duck.”

Dive into Django DB backend

Django DB backend is not tricky. We can identify where functions are saved for later execution. Have a look at django.db.backends.base.base.BaseDatabaseWrapper.on_commit. Postponed functions are stored in self.run_on_commit.

Also it not hard to find out where self.run_on_commit is iterated to execute delayed actions.

Hack it

In this point we are capable to implement a solution forcing the execution of run_and_clear_commit_hooks() iterating through databases connections.

Code is simple, just remember to mock validate_no_atomic_block to fake validation of active transaction.

Enjoy your fast tests

Just test transactions using TestCase and calling run_commit_hook whenever transaction hooks executions is required for testing.

NOTE: Consider setUp might have postponed actions too as they are inside same transaction.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store