Je me moque d'un appel à requests.post
en utilisant la bibliothèque Mock
:
requests.post = Mock()
L'appel implique plusieurs arguments: l'URL, une charge utile, des informations d'authentification, etc. Je veux affirmer que requests.post
est appelé avec une URL particulière, mais je me fiche des autres arguments. Quand j'essaye ceci:
requests.post.assert_called_with(requests_arguments)
le test échoue, car il s'attend à ce qu'il soit appelé avec uniquement cet argument.
Existe-t-il un moyen de vérifier si un seul argument est utilisé quelque part dans l'appel de fonction sans avoir à passer les autres arguments?
Ou, mieux encore, existe-t-il un moyen d'affirmer une URL spécifique, puis des types de données abstraits pour les autres arguments (c'est-à-dire que les données doivent être un dictionnaire, auth doit être une instance de HTTPBasicAuth, etc.)?
Pour autant que je sache, Mock
ne fournit pas un moyen de réaliser ce que vous voulez via assert_called_with
. Vous pouvez accéder à call_args
et call_args_list
membres et effectuez les assertions manuellement.
Cependant, c'est un moyen simple (et sale) d'obtenir presque ce que vous voulez. Vous devez implémenter une classe dont __eq__
la méthode renvoie toujours True
:
def Any(cls):
class Any(cls):
def __eq__(self, other):
return True
return Any()
L'utiliser comme:
In [14]: caller = mock.Mock(return_value=None)
In [15]: caller(1,2,3, arg=True)
In [16]: caller.assert_called_with(Any(int), Any(int), Any(int), arg=True)
In [17]: caller.assert_called_with(Any(int), Any(int), Any(int), arg=False)
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
<ipython-input-17-c604faa06bd0> in <module>()
----> 1 caller.assert_called_with(Any(int), Any(int), Any(int), arg=False)
/usr/lib/python3.3/unittest/mock.py in assert_called_with(_mock_self, *args, **kwargs)
724 if self.call_args != (args, kwargs):
725 msg = self._format_mock_failure_message(args, kwargs)
--> 726 raise AssertionError(msg)
727
728
AssertionError: Expected call: mock(0, 0, 0, arg=False)
Actual call: mock(1, 2, 3, arg=True)
Comme vous pouvez le voir, il ne vérifie que le arg
. Vous devez créer des sous-classes de int
, sinon les comparaisons ne fonctionneront pas1. Cependant, vous devez toujours fournir tous les arguments. Si vous avez de nombreux arguments, vous pouvez raccourcir votre code en utilisant le décompactage de tuple:
In [18]: caller(1,2,3, arg=True)
In [19]: caller.assert_called_with(*[Any(int)]*3, arg=True)
Sauf pour cela, je ne peux pas penser à un moyen d'éviter de passer tous les paramètres à assert_called_with
et travaillez comme vous le souhaitez.
La solution ci-dessus peut être étendue pour vérifier les types d'autres arguments. Par exemple:
In [21]: def Any(cls):
...: class Any(cls):
...: def __eq__(self, other):
...: return isinstance(other, cls)
...: return Any()
In [22]: caller(1, 2.0, "string", {1:1}, [1,2,3])
In [23]: caller.assert_called_with(Any(int), Any(float), Any(str), Any(dict), Any(list))
In [24]: caller(1, 2.0, "string", {1:1}, [1,2,3])
In [25]: caller.assert_called_with(Any(int), Any(float), Any(str), Any(dict), Any(Tuple))
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
<ipython-input-25-f607a20dd665> in <module>()
----> 1 caller.assert_called_with(Any(int), Any(float), Any(str), Any(dict), Any(Tuple))
/usr/lib/python3.3/unittest/mock.py in assert_called_with(_mock_self, *args, **kwargs)
724 if self.call_args != (args, kwargs):
725 msg = self._format_mock_failure_message(args, kwargs)
--> 726 raise AssertionError(msg)
727
728
AssertionError: Expected call: mock(0, 0.0, '', {}, ())
Actual call: mock(1, 2.0, 'string', {1: 1}, [1, 2, 3])
cependant, cela n'autorise pas les arguments qui peuvent être, par exemple, à la fois un int
ou un str
. Autoriser plusieurs arguments à Any
et utiliser l'héritage multiple n'aidera pas. Nous pouvons résoudre ce problème en utilisant abc.ABCMeta
def Any(*cls):
class Any(metaclass=abc.ABCMeta):
def __eq__(self, other):
return isinstance(other, cls)
for c in cls:
Any.register(c)
return Any()
Exemple:
In [41]: caller(1, "ciao")
In [42]: caller.assert_called_with(Any(int, str), Any(int, str))
In [43]: caller("Hello, World!", 2)
In [44]: caller.assert_called_with(Any(int, str), Any(int, str))
1 J'ai utilisé le nom Any
pour la fonction car il est "utilisé comme classe" dans le code. any
est également intégré ...
Vous pouvez également utiliser l'assistant ANY
pour toujours faire correspondre les arguments que vous ne connaissez pas ou que vous ne recherchez pas.
Plus d'informations sur TOUT assistant: https://docs.python.org/3/library/unittest.mock.html#any
Ainsi, par exemple, vous pouvez faire correspondre l'argument "session" à quelque chose comme ça:
from unittest.mock import ANY
requests_arguments = {'slug': 'foo', 'session': ANY}
requests.post.assert_called_with(requests_arguments)
@mock.patch.object(module, 'ClassName')
def test_something(self, mocked):
do_some_thing()
args, kwargs = mocked.call_args
self.assertEqual(expected_url, kwargs.get('url'))
S'il y a trop de paramètres passés et qu'un seul d'entre eux doit être vérifié, faire quelque chose comme {'slug': 'foo', 'field1': ANY, 'field2': ANY, 'field3': ANY, ' . . . }
peut être maladroit.
J'ai adopté l'approche suivante pour y parvenir:
args, kwargs = requests.post.call_args_list[0]
self.assertTrue('slug' in kwargs, 'Slug not passed to requests.post')
En termes simples, cela renvoie un Tuple avec tous les arguments positionnels et un dictionnaire avec tous les arguments nommés passés à l'appel de fonction, vous pouvez donc maintenant vérifier tout ce que vous voulez.
De plus, si vous vouliez vérifier le type de données de quelques champs
args, kwargs = requests.post.call_args_list[0]
self.assertTrue((isinstance(kwargs['data'], dict))
De plus, si vous passez des arguments (au lieu d'arguments de mots clés), vous pouvez y accéder via args
comme ceci
self.assertEqual(
len(args), 1,
'post called with different number of arguments than expected'
)