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.

Related posts