14 March 2008 1 comment Zope
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
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.