glormph
glormph

Reputation: 1004

Why does Django's send_mail not work during testing?

I have an app that imports a number of user email addresses and creates accounts for them. To have them set their own password, I tried to use django's PasswordResetForm (in django.contrib.auth.forms). The password reset is called as soon as a user account has been created:

def reset_password(person):
    form = PasswordResetForm({'email': person.email})
    if form.is_valid():
        form.save(from_email='[email protected]')

I haven't gotten any further with testing than including a unit test that does this:

import password_reset_module
class TestPasswordReset(TestCase):
     def setUp(self):
         p = Person(email='[email protected]')
     def test_send(self):
         password_reset_module.reset_password(p)

No assertions, right now I just want to see if there is mail sent at all by monitoring the console in which I run:

python -m smtpd -n -c DebuggingServer localhost:1025

Saving the form calls django's send_mail. When running the testcase, the send_mail method returns 1. However, no mails show up in the console. The strange thing is that calling send_mail from django's interactive shell:

python manage.py shell

works fine. Mail shows up in the console. Clicking the forgot my password link in a browser also result in sent mails.

I have also tried the file based email backend to no avail. Current settings.py email settings:

EMAIL_USE_TLS = False
EMAIL_HOST = 'localhost'
DEFAULT_FROM_EMAIL = '[email protected]'
EMAIL_HOST_USER = ''
EMAIL_HOST_PASSWORD = ''
EMAIL_PORT = 1025

Now wondering if I am missing something when calling the password reset, or is there a mailserver configuration issue at hands?

Upvotes: 7

Views: 3743

Answers (4)

guettli
guettli

Reputation: 27855

You can use the mailoutbox fixture of pytest-django:

A clean email outbox to which Django-generated emails are sent.

Example

from django.core import mail

def test_mail(mailoutbox):
    mail.send_mail('subject', 'body', '[email protected]', ['[email protected]'])
    assert len(mailoutbox) == 1
    m = mailoutbox[0]
    assert m.subject == 'subject'
    assert m.body == 'body'
    assert m.from_email == '[email protected]'
    assert list(m.to) == ['[email protected]']

Upvotes: 1

Lutz Prechelt
Lutz Prechelt

Reputation: 39336

Your config is being overruled

In Section "Email Services", the Django testing documentation says:

Django's test runner automatically redirects all Django-sent email to a dummy outbox. [...] During test running, each outgoing email is saved in django.core.mail.outbox. This is a simple list of all EmailMessage instances that have been sent.

Huh?

The Django test runner will actually configure a different email backend for you (called locmem). It is very convenient if you want to do unit-testing only (without integration with an actual email server), but very surprising if you don't know it.

(I am not using the Django test runner manage.py test, but it happens anyway, presumably because I have pytest-django installed which magically modifies my py.test.)

If you want to override the overriding and use the email configuration given in your settings module, all you need to re-set is the setting for the email backend, e.g. like this:

@django.test.utils.override_settings(
    EMAIL_BACKEND='django.core.mail.backends.smtp.EmailBackend')
def test_send_email_with_real_SMTP(self):
   ...

Upvotes: 17

robjohncox
robjohncox

Reputation: 3665

It is probably worthwhile trying to turn this into a proper unit test, which of course you can then run as part of your automated test suite. For unit testing, probably the easiest way to check whether the mail was sent (and verify the contents of the mail if required) is to use Django's built-in in memory email backend - you can simply use the outbox attribute on this to get a list of sent mails:

https://docs.djangoproject.com/en/dev/topics/email/#in-memory-backend

This has the advantage of not requiring any infrastructure setup to support testing email sending, makes it very simple to assert the contents of your email, and this should also make the tests fast (when compared to actually sending the emails to an SMTP server).

Hope this helps.

Upvotes: 4

Sukrit Kalra
Sukrit Kalra

Reputation: 34493

From what I understand, you are running the following command while testing the unit.

python -m smtpd -n -c DebuggingServer localhost:1025

This command starts up a "dumb" SMTP server that recieves your emails and displays them on the terminal. Try running your site without this DebuggingServer set up and see if the mails are sent.

Here is the reference to the docs page

Upvotes: 3

Related Questions