Im trying to patch multiple methods in a class. Here is my simplified set up
Hook.py is defined as
class Hook():
def get_key(self):
return "Key"
def get_value(self):
return "Value"
HookTransfer.py defined as
from Hook import Hook
class HookTransfer():
def execute(self):
self.hook = Hook()
key = self.hook.get_key()
value = self.hook.get_value()
print(key)
print(value)
I want to mock the methods get_key and get_value in the Hook class. The following works i.e. prints New_Key and New_Value
from HookTransfer import HookTransfer
import unittest
from unittest import mock
class TestMock(unittest.TestCase):
@mock.patch('HookTransfer.Hook.get_key', return_value="New_Key")
@mock.patch('HookTransfer.Hook.get_value', return_value="New_Value")
def test_execute1(self, mock_get_key, mock_get_value):
HookTransfer().execute()
if __name__ == '__main__':
unittest.main()
However this does not. It prints <MagicMock name='Hook().get_key()' id='4317706896'>
and <MagicMock name='Hook().get_value()' id='4317826128'>
from HookTransfer import HookTransfer
import unittest
from unittest import mock
class TestMock(unittest.TestCase):
@mock.patch('HookTransfer.Hook', spec=True)
def test_execute2(self, mock_hook):
mock_hook.get_key = mock.Mock(return_value="New_Key")
mock_hook.get_value = mock.Mock(return_value="New_Value")
HookTransfer().execute()
if __name__ == '__main__':
unittest.main()
Intuitively it seems like the second one should work too but it doesnt. Could you help explain why it does not. I suspect it has something to do with "where to patch" but Im unable to get clarity.
After some testing I was able to find the issue.
In the second test case, the patch decorator creates a new instance of a Mock class and passes it via mock_hook argument to test_execute2 function. Lets refer to this as mock1. mock1 replaces the Hook class in HookTransfer.py. When self.hook = Hook()
is run, it translates to calling __init__
of mock1. By design this returns yet another Mock instance - lets refer to this as mock2. So self.hook points to mock2. But mock_hook.get_key = mock.Mock(return_value="New_Key")
, mocks the methods in mock1.
In order to mock correctly, mock2 needs to be patched. This can be done in 2 ways
mock_hook.return_value.get_key = mock.Mock(return_value="New_Key")
mock_hook().get_key = mock.Mock(return_value="New_Key")
Under the wraps both options really do the same thing.