在Django中测试电子邮件发送

115
我需要测试我的Django应用程序发送的电子邮件内容是否正确。我不想依赖外部系统(例如临时的gmail帐户),因为我不是在测试实际的电子邮件服务... 我想把这些邮件存储在本地文件夹中,作为它们被发送的一部分。有什么提示可以实现吗?

版主请锁定此问题。许多垃圾信息被添加到答案中,提出了荒谬复杂的解决方案,只是为了推广外部服务。 - nemesisdesign
1
现在所有的内容都在文档中了:https://docs.djangoproject.com/en/stable/topics/testing/tools/#email-services - djvg
10个回答

215

Django测试框架提供了一些内置的助手来帮助您进行测试电子邮件服务

文档中的示例(简短版本):

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.',
            'from@example.com', ['to@example.com'],
            fail_silently=False)
        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(mail.outbox[0].subject, 'Subject here')

3
+1 很好的回答。但是对于复杂情况,当无法使用 send_mail 时,它并不实用。 - santiagobasulto
3
更准确地说,文档在这里:https://docs.djangoproject.com/en/1.8/topics/email/#in-memory-backend。 - nimiq
3
如果你正在测试一个调用send_mail函数的函数,因此无法访问mail,你该如何处理? - Matt
3
当在另一个函数中调用send_mail时,您仍然可以访问mail.outbox - pymarco
3
如果你从核心导入邮件,mail.outbox[0].body会显示发送的电子邮件,即使send_mail在其他地方执行也是如此。 - Rob
这个功能非常完美,即使使用send_messages进行大量发送也没有问题,参见:https://docs.djangoproject.com/en/2.1/topics/email/#sending-multiple-emails - Manel Clos

48
你可以使用一个文件后端发送邮件,这是开发和测试非常方便的解决方案;邮件不会被发送,而是存储在指定的文件夹中!

1
有关电子邮件后端的更多信息:https://docs.djangoproject.com/en/dev/topics/email/#email-backends。 有时甚至简单的控制台后端就足够了。 - Jeewes
1
但是在(自动化)测试期间有没有一种方法可以访问生成的电子邮件? - Overdrivr
@Overdrivr 你可以使用 print 语句在终端中查看生成的电子邮件(例如 print("Mailbox\n", mail.outbox[0].message(), "\n", "_" * 80)),这可以放置在任何 assert 语句之前或之后。self.assertEqual(len(mail.outbox), 1) - Staccato

28

如果你对单元测试感兴趣,最好的解决方案是使用Django提供的内存后端

EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'

以将其用作py.test fixture为例

@pytest.fixture(autouse=True)
def email_backend_setup(self, settings):
    settings.EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'  
在每个测试中,mail.outbox会被重置为服务器状态,因此测试之间没有副作用。

在每个测试中,mail.outbox会被重置为服务器状态,因此测试之间没有副作用。

from django.core import mail

def test_send(self):
    mail.send_mail('subject', 'body.', 'from@example.com', ['to@example.com'])
    assert len(mail.outbox) == 1

def test_send_again(self):
    mail.send_mail('subject', 'body.', 'from@example.com', ['to@example.com'])
    assert len(mail.outbox) == 1

10
使用MailHog

受MailCatcher启发,更容易安装。

使用Go构建 - MailHog在多个平台上运行而无需安装。


此外,它有一个组件称为Jim,即MailHog Chaos Monkey,可以让您测试发送邮件时发生的各种问题:

Jim可以做什么?

  • 拒绝连接
  • 限制连接速率
  • 拒绝验证
  • 拒绝发件人
  • 拒绝收件人

这里阅读更多详细信息。


(与原始的mailcatcher不同,当我发送包含UTF-8编码的表情符号的电子邮件时,它失败了,并且在当前版本中没有真正修复,而MailHog就能正常工作。)

5
对于不需要发送附件的任何项目,我使用 django-mailer,它的优点是所有的出站电子邮件都会进入队列,直到我触发它们的发送,即使它们已经被发送,它们也会被记录下来 - 所有这些都可以在管理员界面中看到,这使得快速检查你的电子邮件代码试图发送到互联网上的内容变得容易。

除此之外,由django-mailer创建的消息对象还意味着您可以在单元测试中检查它们(以及检查其内容)(我知道测试套件中有一个虚拟邮箱的出站邮箱支持,但是使用django-mailer不会发送邮件,除非管理命令发送它,这意味着您不能使用该邮箱对象)。 - Steve Jalim
距离我最初的回答已经过去了一段时间:https://github.com/SmileyChris/django-mailer-2 也支持附件。 - Steve Jalim

4

Django还具有内存中的电子邮件后端。有关详细信息,请参见文档中的In-memory backend。这在Django 1.6中存在,不确定它是否存在于早期版本。


2

1

为什么不通过继承smtpd.SMTPServerthreading.Thread来创建自己的简单SMTP服务器:

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()

每当您的SMTP服务器接收到邮件请求时,都会调用process_message函数,您可以在其中执行任何操作。

在测试代码中,可以这样做:

smtp_server = TestingSMTPServer()
smtp_server.start()
do_thing_that_would_send_a_mail()
smtp_server.close()
self.assertIn(b'hello', smtp_server.received_data)

请记住通过调用smtp_server.close()关闭(close)asyncore.dispatcher以结束asyncore循环(停止服务器监听)。


1
综合几个部分,以下是基于filebased.EmailBackend的简单设置。这会呈现一个列表视图,链接到各个日志文件,它们具有方便的时间戳文件名。在列表中点击链接会在浏览器中显示该消息(原始)。 设置
EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
EMAIL_FILE_PATH = f"{MEDIA_ROOT}/email_out"

视图

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})

模板

{% 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"),

0

如果你有一个可用的TomCat服务器或其他Servlet引擎,那么一个不错的方法是使用"Post Hoc"。它是一个小型服务器,对应用程序而言就像SMTP服务器一样,但它包括一个用户界面,允许您查看和检查已发送的电子邮件消息。它是开源的,可以免费使用。

在这里找到它:Post Hoc GitHub 网站

查看博客文章:PostHoc: 测试发送电子邮件的应用程序


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接