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(spuddans cet exemple) et mon appel despud.foo. J'ai besoin quespudsoit créé avec une méthode mocked-outfoodès le départ, car je ne contrôle pas l'endroit oùspudest 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.