Django - testing emails with the outbox using post_office

Viewed 710

I'm writing tests for an application that uses the django-post_office package for most of its email functionality.

The default django.core.mail library contains plenty of useful tools for testing whether or not there are actually emails being sent. (Without actually sending any during the tests)

class TestFunctionThatSendsEmail(Testcase):

   @override_settings(
        EMAIL_BACKEND='django.core.mail.backends.locmem.EmailBackend'
   )
   def test_function_sends_email(self):
       self.assertEqual(len(outbox), 0)
       run_function_that_calls_email()
       self.assertEqual(len(outbox), 1)
       ...
       # other tests

However, the emails in our function are being sent with the django-post_office mail.send() function

# priority now to make sure that they are being sent right away
mail.send(
        sender=sender,
        recipients=to,
        context=context,
        template=template_name,
        priority='now',
    )

Which causes the test above to fail as for some reason the emails do not end up in the outbox.

The strange thing is that if I change the EMAIL_BACKEND to django.core.mail.backends.console.EmailBackend the emails do show up in my terminal, so it is listening to the EMAIL_BACKEND settings.

I've tried finding alternative methods / functions to test this functionality in the django-post_office github, but all I could find was the advice to check to see if the emails are saved to the database and verify their status. (Which I did and works) but the fact that django seems to be unable to detect any emails actually being sent is making me a little bit nervous.

Does anyone know of a way to make emails sent by django-post_office appear in the outbox or, if that is not possible, a way to make sure that they are actually being sent? (beyond checking the database)

2 Answers

The issue is that django-post_office stores the mail backend in the Email object:

class Email(models.Model):
    backend_alias = models.CharField(_("Backend alias"), blank=True, default='',
                                     max_length=64)

When dispatch() is called, it uses this backend.

When creating an email, DPO overrides create() to set the backend from def get_available_backends(), which looks up the backend in the settings conf.

This means that using @override_settings(EMAIL_BACKEND='django.core.mail.backends.locmem.EmailBackend') won't set the backend_alias in the Email object correctly.

Instead you need to do this manually when you create the object, as per the dispatch test:

def test_dispatch(self):
    """
    Ensure that email.dispatch() actually sends out the email
    """
    email = Email.objects.create(to=['to@example.com'], from_email='from@example.com',
                                 subject='Test dispatch', message='Message', backend_alias='locmem')
    email.dispatch()
    self.assertEqual(mail.outbox[0].subject, 'Test dispatch')

If you are using mail.send() you can simply pass the mail.send(backend='locmem') as an argument; make sure you have locmem': 'django.core.mail.backends.locmem.EmailBackend' in your POST_OFFICE settings

I was having the same issue as you described and fixed it by setting the DEFAULT_PRIORITY to 'now':

class TestFunctionThatSendsEmail(Testcase):

   @override_settings(
        POST_OFFICE={
            'BACKENDS': {'default': 'django.core.mail.backends.locmem.EmailBackend'},
            'DEFAULT_PRIORITY': 'now',
        }
   )
   def test_function_sends_email(self):
       self.assertEqual(len(outbox), 0)
       run_function_that_calls_email()
       self.assertEqual(len(outbox), 1)
       ...
       # other tests
Related