Python 3 urlopen context manager mocking
I was fighting with this as well, and finally figured it out. (Python 3 syntax):
import urllib.requestimport unittestfrom unittest.mock import patch, MagicMockclass TestUrlopen(unittest.TestCase): @patch('urllib.request.urlopen') def test_cm(self, mock_urlopen): cm = MagicMock() cm.getcode.return_value = 200 cm.read.return_value = 'contents' cm.__enter__.return_value = cm mock_urlopen.return_value = cm with urllib.request.urlopen('http://foo') as response: self.assertEqual(response.getcode(), 200) self.assertEqual(response.read(), 'contents') @patch('urllib.request.urlopen') def test_no_cm(self, mock_urlopen): cm = MagicMock() cm.getcode.return_value = 200 cm.read.return_value = 'contents' mock_urlopen.return_value = cm response = urllib.request.urlopen('http://foo') self.assertEqual(response.getcode(), 200) self.assertEqual(response.read(), 'contents') response.close()
here is my take on this
from urllib.request import urlopen from unittest.mock import patchclass Mock(): def __init__(self, request, context): return None def read(self): return self def decode(self, arg): return '' def __iter__(self): return self def __next__(self): raise StopIteration with patch('urllib.request.urlopen', Mock): # do whatever over here
with urlopen('some url here') as data
is a context manager
Also, a file can be used as a context manager, so a better approach here is to use io.StringIO
import ioimport jsonimport urllib.requestfrom unittest.mock import patchdef get_posts(): with urllib.request.urlopen('some url here') as data: return json.load(data)def test_get_posts(): data = io.StringIO('{"id": 123}') with patch.object(urllib.request, 'urlopen', return_value=data): assert get_posts() == {"id": 123}