Here's one of many things I've learnt today at PyCon. Inspired by code that Grig Gheorghiu showed in his slides on automated testing, you can monkey patch a standard library that your application is using in your unit tests to, in my case, mock a remote service without having to run a server. I've done lots of monkey-patching in Zope but then I've only been monkey patching individual methods or attributes of imported classes. This is very similar to that. Here's what my application does:
from poplib import POP3
class MyZopeApp(...):
def check4mail(self, hostname, port, user, pwd):
connection = POP3(hostname, port=port)
...download emails and process them...
Adjacent to this I have a unit/integration test that looks like this:
class TestCase(ZopeTestCase):
def test_check4mail(self):
# monkey patch!
# note that this imports a module, not a class
from Products.IssueTrackerProduct import IssueTracker
FakePOP3.files = ('test1.email',)
IssueTracker.POP3 = FakePOP3
# now check what happens when check4mail() is run
result = self.folder.tracker.check4mail()
assert ...
Now for the mock. The mock is a fake POP3 class that instead of getting its data from the network reads local filesystem files. Here's what the code for FakePOP3
is:
from poplib import POP3, error_proto
class FakePOP3(POP3):
username = 'test'
password = 'test'
files = []
def __init__(self, hostname, port=110):
self.hostname = hostname
self.port = port
def getwelcome(self):
return "Welcome to fake account"
def user(self, user):
if user != self.username:
raise error_proto("Wrong username.")
def pass_(self, pswd):
if pswd != self.password:
raise error_proto("Wrong password.")
def list(self, which=None):
# eg. ('+OK 4 messages:', ['1 71017', '2 2201', '3 7723', '4 44152'], 34)
files = self.files
responses = []
for i, f in enumerate(files):
responses.append('%s %s' % (i+1, os.stat(f)[stat.ST_SIZE]))
return ('+OK %s messages:' % len(files), responses, None)
def retr(self, which):
# ['response', ['line', ...], octets]
filename = self.files[which-1]
return ('response', open(filename, 'r').xreadlines(), None)
def quit(self):
pass
That's it! That's how you fake a POP3 server without having to run an actual mock server which could have been a solution.
Comments
A less advanced alternative is minimock by Ian Bicking:
http://blog.ianbicking.org/minimock.html
For information, this FakePOP3 class is still working great nowadays :) You saved my day !