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.