Mocking a Python standard library

14 March 2008   2 comments   Zope

Mind That Age!

This blog post is 9 years old! Most likely, it's content is outdated. Especially if it's technical.

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:
For information, this FakePOP3 class is still working great nowadays :) You saved my day !
Thank you for posting a comment

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
Bye bye httpretty. Welcome back good old mock! 19 March 2015
EditDistanceMatcher - NodeJS script for doing edit distance 1 matching 05 February 2011
Correction: running Django tests with MongoDB is NOT slow 30 May 2010
Mocking os.stat in Python 08 November 2009 - Using (py)inotify to run commands when files change 20 July 2009
setuptools usability - not good, what can be done? 15 July 2009
To assert or assertEqual in Python unit testing 14 February 2009
Wing IDE versus Jed 11 December 2008
Django vs. Java 25 October 2008
See you at PyCon 2008 11 March 2008