Mock un sous-ensemble des méthodes et propriétés d'une classe Python
J'utilise le mockmodule Python pour effectuer mes tests.
Il y a des moments où je me moque d'une classe, mais je veux juste me moquer de certaines de ses méthodes et propriétés, et pas de toutes.
Supposons le scénario suivant:
# module.py
class SomeClass:
def some_method(self):
return 100
def another_method(self):
return 500
# test.py
class Tests(unittest.TestCase):
@patch('module.SomeClass')
def test_some_operation(self, some_class_mock):
some_class_instance = some_class_mock.return_value
# I'm mocking only the some_method method.
some_class_instance.some_method.return_value = 25
# This is ok, the specific method I mocked returns the value I wished.
self.assertEquals(
25,
SomeClass().some_method()
)
# However, another_method, which I didn't mock, returns a MagicMock instance
# instead of the original value 500
self.assertEquals(
500,
SomeClass().another_method()
)
Sur le code ci-dessus, une fois que j'ai patché la SomeClassclasse, les appels aux méthodes dont je n'ai pas explicitement défini return_values renverront des MagicMockobjets.
Ma question est la suivante: comment puis-je me moquer uniquement de certaines méthodes de classe tout en gardant les autres intactes?
Je peux penser à deux façons, mais aucune n'est vraiment bonne.
Une façon consiste à définir la méthode de la maquette sur la méthode de classe d'origine, comme ceci:
some_class_instance.another_method = SomeClass.another_methodCe n'est pas vraiment souhaitable car la classe peut avoir beaucoup de méthodes et de propriétés à «déprécier».
Une autre façon est de patcher chaque méthode que je veux explicitement, comme:
@patch('module.SomeClass.some_method') def test_some_operation(self, some_method_mock):Mais cela ne fonctionne pas vraiment si je veux me moquer de la classe elle-même, pour des appels moqueurs à l'initialiseur par exemple. Le code ci-dessous remplacerait de toute
SomeClassfaçon toutes les méthodes.@patch('module.SomeClass.some_method') @patch('module.SomeClass') def test_some_operation(self, some_class_mock, some_method_mock):
Voici un exemple plus spécifique:
class Order:
def process_event(self, event, data):
if event == 'event_a':
return self.process_event_a(data)
elif event == 'event_b':
return self.process_event_b(data)
else:
return None
def process_event_a(self, data):
# do something with data
def process_event_b(self, data):
# do something different with data
Dans ce cas, j'ai une méthode générale process_eventqui appelle un événement de traitement spécifique en fonction de l'événement fourni.
Je voudrais tester uniquement la méthode process_event. Je veux juste savoir si l'événement spécifique approprié est appelé en fonction de l'événement que je fournis.
Donc, dans mon cas de test, ce que je veux faire est de simuler juste process_event_aet process_event_b, d'appeler l'original process_eventavec des paramètres spécifiques, puis d'affirmer l'un process_event_aou l' autre ou process_event_bont été appelés avec les paramètres appropriés.
Réponses
Au lieu de patcher toute la classe, vous devez patcher l'objet. À savoir, créez une instance de votre classe, puis corrigez les méthodes de cette instance.
Notez que vous pouvez également utiliser le décorateur à la @patch.objectplace de mon approche.
class SomeClass:
def some_method(self):
return 100
def another_method(self):
return 500
Dans ton test.py
from unittest import mock
class Tests(unittest.TestCase):
def test_some_operation(self):
some_class_instance = SomeClass()
# I'm mocking only the some_method method.
with mock.patch.object(some_class_instance, 'some_method', return_value=25) as cm:
# This is gonna be ok
self.assertEquals(
25,
SomeClass().some_method()
)
# The other methods work as they were supposed to.
self.assertEquals(
500,
SomeClass().another_method()
)