How to spoof nested / multiple layers of returned objects in python

I'm currently trying to find a good way to mock multiple layers / nested return values. In other words, I want to return the magic layout, which in turn returns the magic layout with its own return values. I find this relatively cumbersome and am looking for a more elegant and maintainable solution.

I am trying to effectively test the following code. 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')) 

Here's how I still mocked this, which is extremely inefficient and makes maintenance difficult:

 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 make fun of is the returned data from the decoding function, which comes from the URL opening function. This should be possible on a single line or in a simpler way, using perhaps the input methods. Ideally, the layout will look something like this in the test_load_json function:

 mock_urlopen.__enter__.loads.__enter__.decode.return_value = '["myjsondata"]' 

Unfortunately, I cannot find anything useful in the mock documentation. Any help was appreciated.

+6
source share
2 answers

It turns out that this is easily possible and documented. However, naming is not easy and you need to know what to look for. Bullying refers to coded calls that are actually documented in the mock library.

In this example, mock_urlopen should look like this:

  mock_urlopen.return_value.read.return_value.decode.return_value = '["myjsondata"]' 

It works great. For more information check out the python doc: https://docs.python.org/3/library/unittest.mock-examples.html#mocking-chained-calls

+11
source

I did 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).bcreturn_value = 123 assert ab().c() == 123 
+1
source

All Articles