Developing (a.k.a. Testing) in Python - Part 3: Monkey Patching

DZone 's Guide to

Developing (a.k.a. Testing) in Python - Part 3: Monkey Patching

· Web Dev Zone ·
Free Resource

* This is part 3 of a series on Python test cases.  If you need to catch up, check out part 1 and part 2 before continuing. *

 At Fiesta we run our own custom mailserver. Since we handle a lot of incoming mail, we have to be confident our updates don’t break the mailserver. As can be expected, we have a large amount of test code and run it very often.

dgottlieb@mango-chutney:~$ wc -l ./fiesta/*.py
 11850 total
dgottlieb@mango-chutney:~$ wc -l ./fiesta/test/*.py
 13566 total

We want to test end-to-end delivery of email, but we don’t want our tests leading to a lot of unintentionally sent messages. This is where monkey patching comes in. Because Python has open classes and modules, we can easily redefine methods on the fly. To test our mailserver, we replace some portions of the Python smtplib module during test runs. Let’s take a look at how it’s done:

mailbox = {}
class SMTP(object):
    def sendmail(self, from, recipients, data):
        message = email_parser.from_string(data)
        for recipient in recipients:
	    if recipient not in mailbox:
                mailbox[recipient] = [message]
smtplib.SMTP = SMTP

Here we are actually replacing the entire SMTP object in smtplib with our own custom implementation. Our class defines a sendmail() function that takes the passed in email and adds it to a mailbox dictionary. The dictionary is just a map from email addresses to lists of messages sent to those addresses. In our tests when we expect an email to be sent to someone we simply peek into the mailbox dictionary:

self.assertIn('dan@corp.fiesta.cc', self.mailbox)
self.assertEqual(1, len(self.mailbox['dan@corp.fiesta.cc']))
self.assertEqual('Party On', self.mailbox['dan@corp.fiesta.cc'][0].subject)

Another example of monkey patching in the Fiesta test code is when we test links that will automatically log a user in without requiring a password. These links are generated by concatenating the User ID, the destination page and a time when the link should expire (and then signing the URL to prevent forgery). What we want to test is the timeout portion of these links, but it would be ideal if we did not have to have the test program sleep for any amount of time. This is how we monkey patch the time() method to go forward in time:

auto_login_link = rewrite.add_login('dan@corp.fiesta.cc', '/settings')
user_obj, link, validation_error = rewrite.validate_link(auto_login_link)
self.assertEquals(None, validation_error)

old_time = time.time
time.time = lambda: old_time() + 30 * 24 * 60 * 60 #return a time 1 month in the future
user_obj, link, validation_error = rewrite.validate_link(auto_login_link)
self.assertEquals("Expired Link", validation_error)

time.time = old_time

For those that haven’t had a chance to play with monkey patching, I hope this helps you come up with some clever, but clear (!) ways to test your code. It’s especially useful for testing methods that make library calls that need to exhibit less common/hard to force behavior.

Party on,



source: http://blog.fiesta.cc/post/12336430127/testing-in-python-part-3-monkey-patching


Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}