Enqueuing a Celery task, sending an email, or calling a webhook from a post_save signal or a view runs immediately, while the surrounding transaction is still open. If the transaction later rolls back, the side effect already happened, and for a task queue the worker frequently wins the race and reads a row that does not exist, producing a DoesNotExist that only appears under load.
transaction.on_commit(lambda: task.delay(obj.pk)) defers the callable until the outermost atomic block commits successfully, and drops it entirely on rollback. Outside a transaction it runs immediately, so the same code is correct in both cases.
The testing catch is that TestCase wraps each test in a transaction that is never committed, so on_commit callbacks never run and the assertions silently pass or silently fail depending on what you check. Use django.test.testcases.TestCase.captureOnCommitCallbacks(execute=True) as a context manager to run them, or TransactionTestCase for a real commit. Passing the primary key rather than the object avoids a second class of race in the worker.