Affirmer qu'une fonction / méthode n'a pas été appelée à l'aide de Mock


131

J'utilise la bibliothèque Mock pour tester mon application, mais je veux affirmer qu'une fonction n'a pas été appelée. Les documents simulés parlent de méthodes telles que mock.assert_called_withet mock.assert_called_once_with, mais je n'ai rien trouvé de similaire mock.assert_not_calledou lié à vérifier que la simulation n'a pas été appelée .

Je pourrais aller avec quelque chose comme ce qui suit, même si cela ne semble pas cool ni pythonique:

def test_something:
    # some actions
    with patch('something') as my_var:
        try:
            # args are not important. func should never be called in this test
            my_var.assert_called_with(some, args)
        except AssertionError:
            pass  # this error being raised means it's ok
    # other stuff

Des idées pour y parvenir?


Comme le souligne @Ahmet dans sa réponse, assert_not_called est désormais pris en charge, également dans le backport ( docs.python.org/3/library/… ).
Martin

Réponses:


144

Cela devrait fonctionner pour votre cas;

assert not my_var.called, 'method should not have been called'

Échantillon;

>>> mock=Mock()
>>> mock.a()
<Mock name='mock.a()' id='4349129872'>
>>> assert not mock.b.called, 'b was called and should not have been'
>>> assert not mock.a.called, 'a was called and should not have been'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError: a was called and should not have been

Cette réponse nécessite-t-elle Django? Je reçois une erreur:AttributeError: MockCallable instance has no attribute 'called'
Nathan Arthur

@NathanArthur Hm, je ne pense pas, après sudo easy_install -U mocket from mock import Mocksur MacOS, ce qui précède fonctionne sans accroc. Jamais installé Django :)
Joachim Isaksson

Hmm. C'est étrange. J'utilise Python 2.7.1 et j'utilise unittest et from mock import Mockavec Python Mock 0.1.0 pour mes tests. Est-ce que tout cela semble problématique?
Nathan Arthur

Je me moque d'une classe appelable d'un autre module, il semble donc module_to_test.another_module.class = mock.Mock(), pouvez-vous confirmer que cela ne se souvient pas des appels dans différents cas de test (instances unittest.TestCase)? Je pense que le nombre d'appels ne se réinitialise pas dans ce cas
0xc0de

66

Bien qu'il s'agisse d'une vieille question, je voudrais ajouter que la mockbibliothèque actuellement (backport de unittest.mock) prend en charge la assert_not_calledméthode.

Mettez simplement le vôtre à niveau;

pip install mock --upgrade


29

Vous pouvez vérifier l' calledattribut, mais si votre assertion échoue, la prochaine chose que vous voudrez savoir est quelque chose à propos de l'appel inattendu, vous pouvez donc également faire en sorte que ces informations soient affichées dès le début. En utilisant unittest, vous pouvez vérifier le contenu de à la call_args_listplace:

self.assertItemsEqual(my_var.call_args_list, [])

Quand il échoue, il donne un message comme celui-ci:

AssertionError: les nombres d'éléments n'étaient pas égaux:
Le premier a 0, le second a 1: call ('premier argument', 4)

14

Lorsque vous testez en utilisant la classe hérite unittest.TestCase, vous pouvez simplement utiliser des méthodes telles que:

  • affirmerVrai
  • assertFalse
  • assertEqual

et similaire (dans la documentation python, vous trouvez le reste).

Dans votre exemple, nous pouvons simplement affirmer si la propriété mock_method.called est False , ce qui signifie que la méthode n'a pas été appelée.

import unittest
from unittest import mock

import my_module

class A(unittest.TestCase):
    def setUp(self):
        self.message = "Method should not be called. Called {times} times!"

    @mock.patch("my_module.method_to_mock")
    def test(self, mock_method):
        my_module.method_to_mock()

        self.assertFalse(mock_method.called,
                         self.message.format(times=mock_method.call_count))

11

Avec python >= 3.5vous pouvez utiliser mock_object.assert_not_called().


1

À en juger par d'autres réponses, personne à l'exception de @ rob-kennedy n'a parlé de call_args_list.

C'est un outil puissant pour lequel vous pouvez implémenter exactement le contraire de MagicMock.assert_called_with()

call_args_listest une liste d' callobjets. Chaque callobjet représente un appel effectué sur un appelable simulé.

>>> from unittest.mock import MagicMock
>>> m = MagicMock()
>>> m.call_args_list
[]
>>> m(42)
<MagicMock name='mock()' id='139675158423872'>
>>> m.call_args_list
[call(42)]
>>> m(42, 30)
<MagicMock name='mock()' id='139675158423872'>
>>> m.call_args_list
[call(42), call(42, 30)]

La consommation d'un callobjet est facile, car vous pouvez le comparer avec un tuple de longueur 2 où le premier composant est un tuple contenant tous les arguments de position de l'appel associé, tandis que le second composant est un dictionnaire des arguments de mot-clé.

>>> ((42,),) in m.call_args_list
True
>>> m(42, foo='bar')
<MagicMock name='mock()' id='139675158423872'>
>>> ((42,), {'foo': 'bar'}) in m.call_args_list
True
>>> m(foo='bar')
<MagicMock name='mock()' id='139675158423872'>
>>> ((), {'foo': 'bar'}) in m.call_args_list
True

Ainsi, un moyen de résoudre le problème spécifique du PO est

def test_something():
    with patch('something') as my_var:
        assert ((some, args),) not in my_var.call_args_list

Notez que de cette façon, au lieu de simplement vérifier si un appelable simulé a été appelé, via MagicMock.called, vous pouvez maintenant vérifier s'il a été appelé avec un ensemble spécifique d'arguments.

C'est utile. Supposons que vous souhaitiez tester une fonction qui prend une liste et appeler une autre fonction,, compute()pour chacune des valeurs de la liste uniquement si elles satisfont à une condition spécifique.

Vous pouvez maintenant vous moquer computeet tester si elle a été appelée sur une valeur mais pas sur d'autres.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.