web-dev-qa-db-fra.com

Utilisation de faux pour patcher une tâche de céleri dans Django tests unitaires

J'essaie d'utiliser la bibliothèque python mock pour patcher une tâche Celery qui est exécutée lorsqu'un modèle est enregistré dans mon Django app, pour voir que c'est être appelé correctement.

Fondamentalement, la tâche est définie dans myapp.tasks, Et est importée en haut de mon fichier models.py comme ceci:

from .tasks import mytask

... puis s'exécute sur save() à l'intérieur du modèle à l'aide de mytask.delay(foo, bar). Jusqu'ici tout va bien - ça marche bien quand j'exécute Celeryd, etc.

Je veux construire un test unitaire qui se moque de la tâche, juste pour vérifier qu'elle est appelée avec les arguments corrects, et n'essaie jamais d'exécuter la tâche Celery.

Donc, dans le fichier de test, j'ai quelque chose comme ça à l'intérieur d'un TestCase standard:

from mock import patch # at the top of the file

# ...then later
def test_celery_task(self):
    with patch('myapp.models.mytask.delay') as mock_task:
        # ...create an instance of the model and save it etc
        self.assertTrue(mock_task.called)

... mais il n'est jamais appelé/est toujours faux. J'ai essayé différentes incarnations (patcher myapp.models.mytask À la place et vérifier si mock_task.delay A été appelé à la place. J'ai appris des fausses documentations que le chemin d'importation est crucial, et googler me dit qu'il devrait être le chemin tel qu'il apparaît dans le module sous test (qui serait myapp.models.mytask.delay plutôt que myapp.tasks.mytask.delay, si je comprends bien).

Où vais-je mal ici? Y a-t-il des difficultés spécifiques à patcher des tâches de céleri? Puis-je patcher celery.task (Qui est utilisé comme décorateur pour mytask) à la place?

34
Emil

Le problème que vous rencontrez n'est pas lié au fait qu'il s'agit d'une tâche de céleri. Il se trouve que vous corrigez la mauvaise chose. ;)

Plus précisément, vous devez savoir quelle vue ou autre fichier importe "mytask" et le patcher là-bas, de sorte que la ligne appropriée ressemble à ceci:

with patch('myapp.myview.mytask.delay') as mock_task:

Il y a plus de saveur ici:

http://www.voidspace.org.uk/python/mock/patch.html#where-to-patch

44
Thanos Diacakis

Le décorateur @task Remplace la fonction par un objet Task (voir documentation ). Si vous vous moquez de la tâche elle-même, vous remplacerez l'objet (quelque peu magique) Task par un MagicMock et il ne planifiera pas du tout la tâche. À la place, moquez la méthode run() de l'objet Task, comme ceci:

@override_settings(CELERY_ALWAYS_EAGER=True)
@patch('monitor.tasks.monitor_user.run')
def test_monitor_all(self, monitor_user):
    """
    Test monitor.all task
    """

    user = ApiUserFactory()
    tasks.monitor_all.delay()
    monitor_user.assert_called_once_with(user.key)
28
Danielle Madeley