Testando envio de e-mail em Django [fechado]

92

Preciso testar se meu aplicativo Django envia e-mails com o conteúdo correto. Não quero depender de sistemas externos (como uma conta ad-hoc do gmail ), já que não estou testando o serviço de e-mail real ...

Gostaria de, talvez, armazenar os e-mails localmente, dentro de uma pasta à medida que são enviados. Alguma dica de como conseguir isso?

RadiantHex
fonte
Moderadores: por favor, bloqueiem esta questão. Muito spam está sendo adicionado às respostas, propondo soluções que são ridiculamente complexas apenas para promover serviços externos.
nemesisdesign

Respostas:

42

Você pode usar um backend de arquivo para enviar e-mails, o que é uma solução muito útil para desenvolvimento e teste; e-mails não são enviados, mas armazenados em uma pasta que você pode especificar!

Bernhard Vallant
fonte
1
Mais informações sobre back-ends de e-mail: docs.djangoproject.com/en/dev/topics/email/#email-backends . Às vezes, até mesmo um simples back-end de console é suficiente.
Jeewes
1
Mas existe uma maneira de acessar o e-mail gerado durante o teste (automatizado)?
Overdrivr de
185

A estrutura de teste do Django possui alguns auxiliares integrados para ajudá-lo a testar o serviço de e-mail .

Exemplo de documentos (versão curta):

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')
Davor Lucic
fonte
3
+1 boa resposta. Mas não é útil para casos complexos, quando send_mailnão pode ser usado.
santiagobasulto
3
Mais precisamente, o documento está aqui: docs.djangoproject.com/en/1.8/topics/email/#in-memory-backend
nimiq
3
Como você faria isso se estivesse testando uma função que chama send_mail e, portanto, não pudesse acessar mail?
Matt D
3
@MatthewDrill você ainda pode acessar mail.outboxquando send_mailé chamado em outra função.
pymarco
2
@pymarco Se você importar correio do core, mail.outbox[0].bodymostrará o e-mail enviado mesmo que send_mailseja executado em outro lugar.
Rob
17

Se você está em teste de unidade, a melhor solução é usar o back-end In-memory fornecido pelo django.

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

Considere o caso de usá-lo como um acessório py.test

@pytest.fixture(autouse=True)
def email_backend_setup(self, settings):
    settings.EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'  

Em cada teste, o mail.outboxé redefinido com o servidor, para que não haja efeitos colaterais entre os testes.

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
Kiril
fonte
8

Use MailHog

Inspirado no MailCatcher, mais fácil de instalar.

Construído com Go - MailHog é executado sem instalação em várias plataformas.


Além disso, possui um componente chamado Jim , o MailHog Chaos Monkey , que permite que você teste o envio de e-mails com vários problemas acontecendo:

O que Jim pode fazer?

  • Rejeitar conexões
  • Limite de taxa de conexões
  • Rejeitar autenticação
  • Rejeitar remetentes
  • Rejeitar destinatários

Leia mais sobre isso aqui .


(Ao contrário do mailcatcher original, que falhou ao enviar e-mails com emoji, codificado em UTF-8 e NÃO FOI realmente corrigido na versão atual, o MailHog simplesmente funciona.)

Greg Dubicki
fonte
5

Para qualquer projeto que não requeira o envio de anexos, eu uso o django-mailer , que tem a vantagem de todos os e-mails de saída terminarem em uma fila até que eu acione o envio, e mesmo após terem sido enviados, eles são então registrados - tudo isso é visível no Admin, tornando mais fácil verificar rapidamente o que seu código de e-mail está tentando disparar para os intertubos.

Steve Jalim
fonte
Além disso, os objetos Message criados por django-mailer significam que você pode estimulá-los (e inspecionar seu conteúdo) em testes de unidade também (eu sei que há suporte para caixa de correio de saída no conjunto de teste para uma caixa de correio fictícia, mas usar django-mailer não não envie e-mail a menos que o comando de gerenciamento o envie, o que significa que você não pode usar esse objeto de caixa de correio)
Steve Jalim
Atualização, muito tempo depois da minha resposta original: github.com/SmileyChris/django-mailer-2 também oferece suporte a anexos
Steve Jalim
4

Django também possui um back-end de e-mail em memória. Mais detalhes nos documentos em Back -end In-memory . Isso está presente no Django 1.6 não tenho certeza se está presente em algo anterior.

Josh K
fonte
1

Amarrando algumas das peças aqui juntas, aqui está uma configuração simples baseada em filebased.EmailBackend . Isso renderiza uma exibição de lista com links para os arquivos de log individuais, que têm nomes de arquivo com carimbo de data / hora convenientemente. Clicar em um link na lista exibe essa mensagem no navegador (bruta):

Configurações

EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
EMAIL_FILE_PATH = f"{MEDIA_ROOT}/email_out"

Visão

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

Modelo

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

Por que não iniciar seu próprio servidor SMTP realmente simples herdando de smtpd.SMTPServere 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 é chamado sempre que seu servidor SMTP recebe uma solicitação de email, você pode fazer o que quiser lá.

No código de teste, faça algo assim:

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

Apenas lembre-se close()o asyncore.dispatcherchamando smtp_server.close()para terminar o loop asyncore (parar o servidor de escuta).

frogcoder
fonte
0

Se você tiver um servidor TomCat disponível, ou outro mecanismo de servlet, uma boa abordagem é "Post Hoc", que é um pequeno servidor que parece para o aplicativo exatamente como um servidor SMTP, mas inclui uma interface de usuário que permite visualizar e inspecione as mensagens de email enviadas. É um código aberto e está disponível gratuitamente.

Encontre-o em: Post Hoc GitHub Site

Veja a postagem do blog: PostHoc: Testando aplicativos que enviam e-mail

AgilePro
fonte