Mocking a Python standard library

14 March 2008   2 comments   Zope

Mind That Age!

This blog post is 9 years old! Most likely, its 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.

Follow @peterbe on Twitter


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
Related by Keyword:
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
Related by Text:
Mocking os.stat in Python 08 November 2009
How to unit test the innards of a Django view function 15 November 2008
My tricks for using AsyncHTTPClient in Tornado 13 October 2010
Goodies from tornado-utils - part 1: TestClient 20 September 2011
Nasty surprise of Django cache 09 December 2008