Is er een schone manier om een object te patchen zodat je de assert_call*
-helpers in je testcase krijgt, zonder de actie daadwerkelijk te verwijderen?
Hoe kan ik bijvoorbeeld de regel @patch
wijzigen om de volgende test te laten slagen:
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)
Ik zou dit waarschijnlijk samen kunnen hacken met behulp van side_effect
, maar ik hoopte dat er een leukere manier zou zijn die op dezelfde manier werkt voor alle functies, klassenmethoden, statische methoden, ongebonden methoden, enz.
Antwoord 1, autoriteit 100%
Vergelijkbare oplossing als die van jou, maar met 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)
Volgens de documentatie:
wraps: item voor het namaakobject om in te pakken. Als wraps niet Geen is, dan
door de Mock te bellen, wordt de oproep doorgestuurd naar het ingepakte object
(teruggeven van het echte resultaat). Attribuuttoegang op de mock zal terugkeren
een Mock-object dat het corresponderende attribuut van de wrap omwikkelt
object (dus proberen toegang te krijgen tot een attribuut dat niet bestaat zal
een AttributeError opwerpen).
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)
Antwoord 2, autoriteit 24%
Dit antwoord beantwoordt aan de aanvullende vereiste die wordt genoemd in de premie van gebruiker Quuxplusone:
Het belangrijkste voor mijn use-case is dat het werkt met
@patch.mock
, dat wil zeggen dat ik geen code hoef in te voegen tussen mijn constructie van de instantie vanPotato
(spud
in dit voorbeeld) en mijn aanroep vanspud.foo
. Ik hebspud
nodig om vanaf het begin te worden gemaakt met een nagemaaktefoo
-methode, omdat ik geen controle heb over de plaats waarspud
is gemaakt.
De hierboven beschreven use case kan zonder al te veel moeite worden bereikt door een decorateur te gebruiken:
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()
Als de vervangen methode veranderlijke argumenten accepteert die tijdens het testen zijn gewijzigd, wil je misschien een CopyingMock
*in plaats van de MagicMock
in de spy_decorator.
*Het is een recept uit de docsdie ik op PyPI heb gepubliceerd als copyingmocklib
Antwoord 3, autoriteit 3%
De OP doet dit verzoek:
Ik zou dit waarschijnlijk samen kunnen hacken met
side_effect
Voor degenen die het niet erg vinden om side_effect
te gebruiken, hier is een oplossing met een paar voordelen:
- Gebruikt decorateur-syntaxis
- Past een ongebonden methode op, die volgens mij veelzijdiger is
- Vereist opname van de instantie in de bewering
class PotatoTest(TestCase):
@patch.object(Potato, 'foo', side_effect=Potato.foo, autospec=True)
def test_something(self, mock):
spud = Potato()
forty_two = spud.foo(n=40)
mock.assert_called_once_with(spud, n=40)
self.assertEqual(forty_two, 42)