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

Peter Bengtsson

A less advanced alternative is minimock by Ian Bicking:
http://blog.ianbicking.org/minimock.html

romainhk

For information, this FakePOP3 class is still working great nowadays :) You saved my day !

Your email will never ever be published.

Previous:
See you at PyCon 2008 March 11, 2008 Python
Next:
apple.com/store doesn't work in Firefox (on Linux) March 19, 2008 Web development
Related by category:
'Cache-Control' or Expires September 16, 2005 Zope
Plone.org calls it Issue Tracker May 14, 2005 Zope
To all Zope developers: Does this sound familiar? March 8, 2011 Zope
IssueTrackerProduct now officially abandoned March 30, 2012 Zope
Related by keyword:
To assert or assertEqual in Python unit testing February 14, 2009 Python
Mocking os.stat in Python November 8, 2009 Python
Bye bye httpretty. Welcome back good old mock! March 19, 2015 Python
setuptools usability - not good, what can be done? July 15, 2009 Python