Testing Django Emails

In the past, my experience testing the sending of emails from web frameworks had not been a particularly smooth or easy one. There was often a bit of hackery involved, and attempting to do so from a test harness of some sort was often tricky. Fortunately, testing emails from Django (1.0+) test cases is pretty straightforward and very pleasant to use.

When you run the Django testrunner, the runner will temporarily override the SMTPConnection class. Any emails sent from code called by the testrunner will not be sent out, but captured and stored for analysis within your tests.

Let’s say I have some code that will send out a notification email, and I want to test that it works. I could have a test case along the lines of:

class NotificationEmailTest( TestCase ):
    def test_send_notification_email( self ):
        send_notification_email( "Foo!" )
        ## test goes here

But how do I check the email was sent? Well, the overridden SMTPConnection stores emails in the django.core.mail.outbox list. I can therefore check to see if I have one email message in the outbox. This outbox is only available within tests, it does not exist in normal Django operation.

First, I need an import so I can access the outbox:

from django.core import mail

Then I have to revise the above test to include my assertion:

class NotificationEmailTest( TestCase ):
    def test_send_notification_email( self ):
        """ Test send_notification_email sends out one email """
        send_notification_email( "Foo!" )

        self.assertEqual( len(mail.outbox), 1 )

The outbox is guaranteed to be empty at the start of each test, so you don’t need to do anything in terms of setup or teardown.

Testing presence of an email is fine, but we normally want to test the content of a message (otherwise it could be junk!). Each item in the outbox is an instance of EmailMessage, with all the expected attributes. If we wanted to add a second assertion to the above test, say to check the subject, we could do so:

class NotificationEmailTest( TestCase ):
    def test_send_notification_email( self ):
        """ Test send_notification_email sends out one email """
        send_notification_email( "Foo!" )

        self.assertEqual( len(mail.outbox), 1 )
        self.assertEqual( mail.outbox[0].subject, "Foo!" )

In this case, the single parameter for send_notification_email is actually the subject of the email, so my test checks to see if this matches.

When testing the contents of to and bcc, there is apparently no cc, be aware these are tuples of addresses when writing your tests.

class NotificationEmailTest( TestCase ):
    def test_send_notification_email( self ):
        """ Test send_notification_email sends out one email """
        send_notification_email( "Foo!" )

        self.assertEqual( len(mail.outbox), 1 )
        self.assertEqual( mail.outbox[0].to, [ "test@foo.bar" ] )

This is fine if we have one recipient, but if our recipient list is going to have multiple addresses we need to allow for this in our tests. If we knew the exact ordering we could compare to a list but, unless the ordering matters, we end up making the test fragile. Instead, we can test for the presence of email addresses individually:

        self.assertTrue( "test@foo.bar" in mail.outbox[0].to )
        self.assertTrue( "wibble@amiga.foo" in mail.outbox[0].to )

Django makes testing of emails easy, so there’s no excuse for not incorporating email tests into your Django test suites.

Hope that gives you a starting point. Have fun!

Further reading:

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s