Python mock-Patcher une méthode sans entraver l'implémentation
Existe-t-il un moyen propre de patcher un objet afin d'obtenir les aides assert_call*
dans votre cas de test, sans réellement supprimer l'action?
Par exemple, comment puis-je modifier la ligne @patch
pour obtenir le test suivant:
from unittest import TestCase
from mock import patch
class Potato(object):
def foo(self, n):
return self.bar(n)
def bar(self, n):
return n + 2
class PotatoTest(TestCase):
@patch.object(Potato, 'foo')
def test_something(self, mock):
spud = Potato()
forty_two = spud.foo(n=40)
mock.assert_called_once_with(n=40)
self.assertEqual(forty_two, 42)
Je pourrais probablement pirater cela ensemble en utilisant side_effect
, mais j'espérais qu'il y aurait un moyen plus agréable qui fonctionne de la même manière sur toutes les fonctions, classmethods, staticmethods, méthodes non liées, etc.
2 réponses
Solution similaire avec la vôtre, mais en utilisant wraps
:
def test_something(self):
spud = Potato()
with patch.object(Potato, 'foo', wraps=spud.foo) as mock:
forty_two = spud.foo(n=40)
mock.assert_called_once_with(n=40)
self.assertEqual(forty_two, 42)
Selon la documentation:
wraps : élément pour l'objet fictif à envelopper. Si wraps n'est pas None alors l'appel de la maquette passera l'appel à l'objet encapsulé (retour du résultat réel). L'accès aux attributs sur le mock retournera un objet simulé qui enveloppe l'attribut correspondant de l'encapsulé objet (donc tenter d'accéder à un attribut qui n'existe pas génère une erreur AttributeError).
class Potato(object):
def spam(self, n):
return self.foo(n=n)
def foo(self, n):
return self.bar(n)
def bar(self, n):
return n + 2
class PotatoTest(TestCase):
def test_something(self):
spud = Potato()
with patch.object(Potato, 'foo', wraps=spud.foo) as mock:
forty_two = spud.spam(n=40)
mock.assert_called_once_with(n=40)
self.assertEqual(forty_two, 42)
Cette réponse répond à l'exigence supplémentaire mentionnée dans la prime de l'utilisateur Quuxplusone:
La chose importante pour mon cas d'utilisation est qu'il fonctionne avec
@patch.mock
, c'est-à-dire qu'il ne m'oblige pas à insérer du code entre ma construction de l'instance dePotato
(spud
dans cet exemple) et mon appel despud.foo
. J'ai besoin quespud
soit créé avec une méthode mocked-outfoo
dès le départ, car je ne contrôle pas l'endroit oùspud
est créé.
L'utilisation le cas décrit ci-dessus pourrait être réalisé sans trop de problèmes en utilisant un décorateur:
import unittest
import unittest.mock # Python 3
def spy_decorator(method_to_decorate):
mock = unittest.mock.MagicMock()
def wrapper(self, *args, **kwargs):
mock(*args, **kwargs)
return method_to_decorate(self, *args, **kwargs)
wrapper.mock = mock
return wrapper
def spam(n=42):
spud = Potato()
return spud.foo(n=n)
class Potato(object):
def foo(self, n):
return self.bar(n)
def bar(self, n):
return n + 2
class PotatoTest(unittest.TestCase):
def test_something(self):
foo = spy_decorator(Potato.foo)
with unittest.mock.patch.object(Potato, 'foo', foo):
forty_two = spam(n=40)
foo.mock.assert_called_once_with(n=40)
self.assertEqual(forty_two, 42)
if __name__ == '__main__':
unittest.main()
Si la méthode replaced accepte des arguments mutables qui sont modifiés en cours de test, vous pouvezCopyingMock
à la place du MagicMock
à l'intérieur du spy_decorator.