I need to test that my Django application sends e-mails with correct content. I don't want to rely on external systems (like an ad-hoc gmail account), since I'm not testing the actual e-mail service...
I would like to, maybe, store the emails locally, within a folder as they are sent. Any tip on how to achieve it?
In most cases, you can send email using django. core. mail. send_mail() .
How to send multiple mass emails django. We need to create a Tuple of messages and send them using send mass mail. In this tutorial, we create a project which sends email using Django. We fill the data in the form and send it using Django Email.
Django test framework has some built in helpers to aid you with testing e-mail service.
Example from docs (short version):
from django.core import mail
from django.test import TestCase
class EmailTest(TestCase):
def test_send_email(self):
mail.send_mail('Subject here', 'Here is the message.',
'[email protected]', ['[email protected]'],
fail_silently=False)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, 'Subject here')
You can use a file backend for sending emails which is a very handy solution for development and testing; emails are not sent but stored in a folder you can specify!
If you are into unit-testing the best solution is to use the In-memory backend provided by django.
EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'
Take the case of use it as a py.test fixture
@pytest.fixture(autouse=True)
def email_backend_setup(self, settings):
settings.EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'
In each test, the mail.outbox
is reset with the server, so there are no side effects between tests.
from django.core import mail
def test_send(self):
mail.send_mail('subject', 'body.', '[email protected]', ['[email protected]'])
assert len(mail.outbox) == 1
def test_send_again(self):
mail.send_mail('subject', 'body.', '[email protected]', ['[email protected]'])
assert len(mail.outbox) == 1
Use MailHog
Inspired by MailCatcher, easier to install.
Built with Go - MailHog runs without installation on multiple platforms.
Also, it has a component called Jim, the MailHog Chaos Monkey, which enables you to test sending emails with various problems happening:
What can Jim do?
- Reject connections
- Rate limit connections
- Reject authentication
- Reject senders
- Reject recipients
Read more about it here.
(Unlike original mailcatcher, which failed on me when sending emails with emoji, encoded in UTF-8 and it WASN'T really fixed in the current release, MailHog just works.)
For any project that doesn't require sending attachments, I use django-mailer, which has the benefit of all outbound emails ending up in a queue until I trigger their sending, and even after they've been sent, they are then logged - all of which is visible in the Admin, making it easy to quickly check what you emailing code is trying to fire off into the intertubes.
Django also has an in-memory email backend. More details in the docs under In-memory backend. This is present in Django 1.6 not sure if it's present in anything earlier.
Patching SMTPLib for testing purposes can help test sending mails without sending them.
Why not start your own really simple SMTP Server by inherit from smtpd.SMTPServer
and threading.Thread
:
class TestingSMTPServer(smtpd.SMTPServer, threading.Thread):
def __init__(self, port=25):
smtpd.SMTPServer.__init__(
self,
('localhost', port),
('localhost', port),
decode_data=False
)
threading.Thread.__init__(self)
def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
self.received_peer = peer
self.received_mailfrom = mailfrom
self.received_rcpttos = rcpttos
self.received_data = data
def run(self):
asyncore.loop()
process_message is called whenever your SMTP Server receive a mail request, you can do whatever you want there.
In the testing code, do something like this:
smtp_server = TestingSMTPServer()
smtp_server.start()
do_thing_that_would_send_a_mail()
smtp_server.close()
self.assertIn(b'hello', smtp_server.received_data)
Just remember to close()
the asyncore.dispatcher
by calling smtp_server.close()
to end the asyncore loop(stop the server from listening).
Tying a few of the pieces here together, here's a straightforward setup based on filebased.EmailBackend
. This renders a list view linking to the individual log files, which have conveniently timestamped filenames. Clicking a link in the list displays that message in the browser (raw):
Settings
EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
EMAIL_FILE_PATH = f"{MEDIA_ROOT}/email_out"
View
import os
from django.conf import settings
from django.shortcuts import render
def mailcheck(request):
path = f"{settings.MEDIA_ROOT}/email_out"
mail_list = os.listdir(path)
return render(request, "mailcheck.html", context={"mail_list": mail_list})
Template
{% if mail_list %}
<ul>
{% for msg in mail_list %}
<li>
<a href="{{ MEDIA_URL }}email_out/{{msg}}">{{ msg }}</a>
</li>
{% endfor %}
</ul>
{% else %}
No messages found.
{% endif %}
urls
path("mailcheck/", view=mailcheck, name="mailcheck"),
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With