Reputation: 6043
My project makes various calls to external API's using Python's urllib2.urlopen
. I'm using NoseTests for my unit testing and MiniMock to mock up calls made to urllib2.urlopen
.
The mocking code:
from hashlib import md5
from os.path import dirname, join
from urllib2 import Request, urlopen
from minimock import mock, restore
def urlopen_stub(url, data=None, timeout=30):
"""
Mock urllib2.urlopen and return a local file handle or create file if
not existent and then return it.
"""
if isinstance(url, Request):
key = md5(url.get_full_url()).hexdigest()
else:
key = md5(url).hexdigest()
data_file = join(dirname(__file__), 'cache', '%s.xml' % key)
try:
f = open(data_file)
except IOError:
restore() # restore normal function
data = urlopen(url, data).read()
mock('urlopen', returns_func=urlopen_stub, tracker=None) # re-mock it.
with open(data_file, 'w') as f:
f.write(data)
f = open(data_file, 'r')
return f
mock('urlopen', returns_func=urlopen_stub, tracker=None)
I'm running my tests like so:
from os.path import isfile, join
from shutil import copytree, rmtree
from nose.tools import assert_raises, assert_true
import urlopenmock
class TestMain(object):
working = 'testing/working'
def setUp(self):
files = 'testing/files'
copytree(files, self.working)
def tearDown(self):
rmtree(self.working)
def test_foo(self):
func_a_calling_urlopen()
assert_true(isfile(join(self.working, 'file_b.txt')))
def test_bar(self):
func_b_calling_urlopen()
assert_true(isfile(join(self.working, 'file_b.txt')))
def test_bar_exception(self):
assert_raises(AnException, func_c_calling_urlopen)
Originally I had the test checking for the exception in a separate module which imported a different mocking file that returned a broken XML file when urlopen
was called. However importing that mocking class overrode the one shown above, breaking all the tests as the broken XML was used each time.
I assume this was because the exception testing module was loaded after the others thus it's import was called last and the mocked function returning the broken XML overrode the original mocked function.
I'd like to be able to tell the mocking code to use the broken XML file when test_bar_exception is run so that it raises the exception. How would I go about doing this?
Upvotes: 2
Views: 3431
Reputation: 8824
Assume that your input to request is 'a url' and output would be 'aresponse', and for input 'burl' output is 'bresponse', so use
@mock.patch('requests.get', mock.Mock(side_effect = lambda k:{'aurl': 'a response', 'burl' : 'b response'}.get(k, 'unhandled request %s'%k)))
Upvotes: 0
Reputation: 2203
Seems to me that you will need to setup and teardown your mock urlopen on each test that needs to use it and have a different mock urlopen to return a broken xml file for those tests dealing with that error condition. Something like:
class TestMain(object):
# ...
def test_foo(self):
mock('urlopen', returns_func=urlopen_stub, tracker=None)
func_a_calling_urlopen()
assert_true(isfile(join(self.working, 'file_b.txt')))
restore()
# ...
def test_bar_exception(self):
mock('urlopen',
returns_func=urlopen_stub_which_returns_broken_xml_file,
tracker=None)
assert_raises(AnException, func_c_calling_urlopen)
restore()
There is a problem with the above, however, if a test raises an exception and the restore()
call is not reached. You could wrap with try: ... finally:
however, that's a lot of red-tape for each test.
You might want to take a look Michael Foord's mock library.
Pycon presentation: http://blip.tv/file/4881513
Take a look at patch which gives you decorator and context-manager options for replacing and restoring your calls on urlopen. Its very nifty!
Upvotes: 3