paste.fixture's Dummy_smtplib

paste.fixture's Dummy_smtplib

I’m working on two webapps that need to send email to users, and in both, the user is expected to click on a URL to confirm their registration. A common enough idiom for website accounts.

As a disciple of the cult of test-driven development, I want to be able to make a test that generates that email, inspects the contents for the URL, visits that generated URL, and then checks that the registration is completed.

Michael K, who previously suggested other abuses of Python’s dynamic nature, reminded me a few months ago that you could monkeypatch an imported library with your own, and it’d be preserved throughout the “address space” of the running Python program.

I’d not really played with it, and had been putting off writing a test for email because I didn’t really understand what I wanted. The other night at DebSIG, I asked (complained?) again about it, and he said “Hey, paste.fixture already does it!” I’d said I knew, but it had no documentation, and no-one on the paste users list had responded to my request for examples. He gave me a curt “Read the fucking source” response (in a much nicer way of course) and I thought, he’s right! Back in the day I used to read library source code in order to work out poorly documented APIs, why now do I rely on clear documentation so much? I should just dig in and write some example code to test it out, and JFDI.

Enough of the backstory.

Here’s a quick guide to setting up an email sender test, using paste.fixture around your Pylons application.

Firstly, in your controller, you have something that sends email, like so:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import smtplib

from app.lib.base import BaseController, m

class FooController(BaseController):
    def index(self):
        """Do something and send email while you're at it"""
        s = smtplib.SMTP("localhost")
        
        s.sendmail("from@example.org",
                   "to@example.com",
                   """
Hey, this is a message

http://localhost/foo/activate/37
"""
        s.quit()
        
        m.subexec("foo/index.myt")

    def activate(self, id):
        m.write("awesome, activating %s" % id)

Pretty basic, if we visit /foo/ then we send an email, and then if we visit /foo/activate/N we inform the visitor of the activation.

The test is pretty simple too:

1
2
3
4
5
6
7
import re
import unittest

from paste.fixture import Dummy_smtplib

def TestFooController(unittest.TestCase):
    def test_foo_activation(self):

You just call the classmethod install on Dummy_smtplib to set it up, which does some magic behind the scenes (really it just replaces smtplib.SMTP with itself)

8
        Dummy_smtplib.install()

then run through the process you want to test

10
11
        # get the start page
        res = self.app.get('/foo')

and now we check that the message was sent, and its contents

13
14
15
16
17
18
19
        self.failIfEqual(None, Dummy_smtplib.existing, "no message sent")
        
        match = re.match(r'^.*/foo/activate/([^ ]+)', Dummy_smtplib.existing.message)
        self.failIfEqual(None, match)
        
        # visit the URL
        res = self.app.get('/foo/activate/%s' % match.group(1))

and finally, test the result of the activation.

20
21
        res.mustcontain('awesome')
        res.mustcontain(match.group(1))

You’ve also got to clean up, reset the dummy SMTP library for next time (you’ll get an exception thrown if you don’t, to remind you).

20
        Dummy_smtplib.existing.reset()

If you do any database stuff, then the times to check the status of the data model are just before the actuvation URL is visited, and again afterwards. I keep the model empty for each test, so I can pull out all the records and make sure that there’s only one afterwards, and that it has the right attributes before and after.

Pretty easy stuff.