I'm currently struggling to find a good way of mocking multiple layers / nested return values. In other words, I want to return a magic mock that in turn returns a magic mock with it's own set return values. I'm finding this relatively cumbersome and am looking for a more elegant and maintainable solution.
I'm trying to test the following code efficiently. the URL returns a json string that needs further processing:
import json
from urllib.request import url open
def load_json():
# first return value
response = urlopen("http://someurl.com/api/getjson")
# in turn, contains two nested return values for read and decode
response_dict = json.loads(response.read().decode('utf-8'))
This is how I've mocked this so far, which is extremely inelegant and makes maintenance complicated:
class MyTestCase(TestCase):
@patch('load_json_path.urlopen')
def test_load_json(self, mock_urlopen):
### trying to simplify all of this
# third nested return
mock_decode = MagicMock(return_value='["myjsondata"]')
# second nested return value
mock_response = MagicMock()
mock_response.read.return_value=mock_decode
# first nested return value
mock_urlopen.return_value = mock_response
### trying to simplify all of this
load_json()
In the end, all i'm trying to mock is the returned data from the decode function, that originates from the url open function. This should be possible in one line or in a simpler way, using perhaps the enter methods. Ideally the mock would look something like this in the test_load_json function:
mock_urlopen.__enter__.loads.__enter__.decode.return_value = '["myjsondata"]'
Unfortunately, I can't seem to find anything useful in the mock documentation. Any help appreciated.
Turns out this is easily possible and documented. However, the naming is not straightforward and needed to know what one is looking for. The referred to mocking is chained calls, which are in fact documented in the mock library.
In this example, the mock_urlopen should look like this:
mock_urlopen.return_value.read.return_value.decode.return_value = '["myjsondata"]'
This works beautifully. For more details check out the python doc: https://docs.python.org/3/library/unittest.mock-examples.html#mocking-chained-calls
I have made this for you as a helper class:
from unittest.mock import Mock
class ProxyMock:
"""Put me for easy referral"""
def __init__(self, mock, _first=True):
self._mock_ = mock
self._first_ = _first
def __getattr__(self, name):
if self._first_:
new_mock = getattr(self._mock_, name)
else:
new_mock = getattr(self._mock_.return_value, name)
return ProxyMock(new_mock, _first=False)
def __setattr__(self, name, value):
if name in ("_mock_", "_first_"):
return super().__setattr__(name, value)
setattr(self._mock_, name, value)
a = Mock()
ProxyMock(a).b.c.return_value = 123
assert a.b().c() == 123
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With