Patch EmailMultiAlternatives send() to raise exception in Celery Task

Good morning,

I am trying to test a Celery Task by raising an SMTPException when sending an email.

Say I have the following code:

located in: my_app.mailer.tasks

from django.core.mail import EmailMultiAlternatives

@app.task(bind=True )
def send_mail(self):
    subject, from_email, to = 'hello', 'from@example.com', 'to@example.com'
    text_content = 'This is an important message.'
    html_content = '<p>This is an <strong>important</strong> message.</p>'
    msg = EmailMultiAlternatives(subject, text_content, from_email, [to])
    msg.attach_alternative(html_content, "text/html")
    try:
        msg.send(fail_silently=False)
    except SMTPException as exc:
        print('Exception ', exc)

and I run the following test:

class SendMailTest(TestCase):

    @patch('my_app.mailer.tasks.EmailMultiAlternatives.send')
    def test_task_state(self, mock_send):
        mock_send.side_effect = SMTPException()
        task = send_mail.delay()
        results = task.get()
        self.assertEqual(task.state, 'SUCCESS')

The email is sent without error.

However, if I turn the task into a standard function and then run the following test against it:

now located in: my_app.mailer.views

class SendMailTest(TestCase):

    @patch('myapp.mailer.views.EmailMultiAlternatives.send')
    def test_task_state(self, mock_send):
        mock_send.side_effect = SMTPException()
        send_mail(fail_silently=False)

The string ‘Exception’ is displayed, but there is no exc information as to what caused the exception.

Please, what am I doing wrong?

Can you elaborate on what want to happen?

The exception is effectively being raised by this line:

mock_send.side_effect = SMTPException()

That indicates that when the mocked function is called, it should raise an exception, specifically the instance of SMTPException that’s created on that line.

Because that instance has no other information, that’s likely why you’re not seeing additional information in the print statement that you’re used to seeing. Does that make sense?

Hi Tim,

Many thanks for taking the time to look at this.

That makes perfect sense, so what I need to do is change:

mock_send.side_effect = SMTPException()
to;
mock_send.side_effect = Exception(SMTPException)

Which results in the expected output of; 

Exception  <class 'smtplib.SMTPException'>

My primary question is, how do I raise the same exception on the Celery Task that I included in the first part of this post.
As things stand, despite the patched exception, the Task successfully sends an email.
Thanks again.

What about bypassing the patching and using this to confirm the task’s functionality Testing tools | Django documentation | Django?

I found this to be the best solution:

@patch('my_app.mailer.tasks.EmailMultiAlternatives.send')
def test_smtp_exception(self, alt_send):
    with self.assertLogs(logger='celery.app.trace') as cm:
        alt_send.side_effect = SMTPException(SMTPException)
        task = send_mail.s(kwargs=self.message).apply()
        exc = cm.output

        self.assertIn('Retry in 1s', exc[0])
        self.assertIn('Retry in 2s', exc[1])
        self.assertIn('Retry in 4s', exc[2])
        self.assertIn('Retry in 8s', exc[3])