Mocking a Python standard library

14 March 2008   1 comment   Zope

Powered by Fusion×

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) 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 = ('',)
       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):

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.


Peter Bengtsson
A less advanced alternative is minimock by Ian Bicking:

Your email will never ever be published

Related posts

See you at PyCon 2008 11 March 2008
Next: doesn't work in Firefox (on Linux) 19 March 2008
Related by keywords:
To assert or assertEqual in Python unit testing 14 February 2009
Now I have a Gmail account 29 June 2004
Mocking os.stat in Python 08 November 2009
setuptools usability - not good, what can be done? 15 July 2009
EditDistanceMatcher - NodeJS script for doing edit distance 1 matching 05 February 2011
Wing IDE versus Jed 11 December 2008
Correction: running Django tests with MongoDB is NOT slow 30 May 2010
Gmail spam filter 13 January 2005 - Using (py)inotify to run commands when files change 20 July 2009
Django vs. Java 25 October 2008
IssueTrackerProduct 0.7 released 16 December 2005
Google London Automation Test conference 08 September 2006