Python 如何测试电子邮件是否在执行芹菜任务后发送

Python 如何测试电子邮件是否在执行芹菜任务后发送,python,django,unit-testing,celery,Python,Django,Unit Testing,Celery,我用的是Django 1.10和芹菜4.1 我有一个共享任务,它向用户发送电子邮件 # myapp/tasks.py @shared_task def notify_user(user_id): # TODO: send email and do other stuff here user = get_object_or_404(User, pk=user_id) send_mail( 'Subject', 'Body',

我用的是Django 1.10和芹菜4.1

我有一个
共享任务
,它向用户发送电子邮件

# myapp/tasks.py
@shared_task
def notify_user(user_id):
    # TODO: send email and do other stuff here
    user = get_object_or_404(User, pk=user_id)

    send_mail(
        'Subject',
        'Body',
        'from@example.com',
        [user.email],
    )
我有另一个文件,其中包含一个函数,调用该函数将任务放入队列

# myapp/utils.py
# ...
def update_queue(self):
    # increment no_of_used_referrals by 1
    if no_of_used_referrals == 5:
        notify_user.apply_async((self.user_id,))
    else:
        notify_user.apply_async((self.user_id,), eta=new_eta)
现在,我正在尝试测试调用
update\u queue()
(所有必需的检查都通过了)是否会在执行时向用户发送电子邮件

我试着做到以下几点:

# myapp/tests.py
def update_queue_should_call_notify_user_immediately_after_five_referrals_were_used(self):
    with unittest.mock.patch('myapp.tasks.notify_user.apply_async') as notify_user_mock:
        # ...
        for _ in range(5):
            entry.update_queue()
        self.assertTrue(notify_user_mock.called)
        notify_user_mock.assert_called_with((user_id,))
    # TODO: check if email was sent

    # I tried using : 
    # self.assertEqual(len(mail.outbox), 1) 
    # but it fails with error saying 0 != 1
def test_notify_user_should_send_an_email(self):
    notify_user.apply_async((user_id,))

    # I tried using:
    # self.assertEqual(len(mail.outbox), 1)
    # but it fails with error saying 0 != 1
我在项目设置中设置了
EMAIL\u BACKEND='django.core.mail.backends.locmem.EmailBackend'

有人能告诉我我所做的有什么问题,以及如何正确地测试这个案例吗

编辑 正如@DanielRoseman所建议的,我已经更新了我的代码,排除了模仿

EDIT2 请参阅上面更新的文件

我正在模拟转诊系统。一旦使用了与特定用户关联的5个推荐链接,用户就可以在其个人资料中获得一些不错的功能。否则,它们必须等待一个特定的时间,我使用
apply\u async
上的
eta
参数设置这个时间

每次我调用
update_queue
时,我都会检查引用的数量是否等于5(请参见上面更新的代码)

  • 如果是-我想立即调用
    notify_user
    ,而不传递
    eta
    参数值
  • 如果不是-I,则增加已使用的引用链接数,撤销旧的
    notify\u用户
    任务,使用新的
    eta
    参数值创建新的
    notify\u用户
    任务

为了测试我正在模拟for循环中的行为,我想测试在5次迭代(相当于5个使用过的引用链接)之后是否向用户发送了电子邮件(出于测试目的,我使用内存后端发送电子邮件)。

我将其放在这里,以供面临相同问题的人使用

我已经解决了

TEST_RUNNER = 'djcelery.contrib.test_runner.CeleryTestSuiteRunner'

我认为此解决方案适用于单元测试。

tasks.py test_tasks.py
我能想到的最好的方法是,你可以为相应的用户在数据库中添加一个字段,告诉他们电子邮件是否发送给用户。然后,您可以在芹菜任务中更新此字段,这样,如果发送电子邮件成功,则该字段将更新。这很容易测试。你已经模拟了整个任务,所以很自然,它实际上什么都不会做。你不应该嘲笑测试中的代码。@DanielRoseman我已经更新了答案。但它仍然不起作用。@Arpitolanki谢谢你的建议,但这听起来像是一个不必要的开销,仅用于测试目的。如果我将该属性实现到我的一个数据库模型中,我将永远不会在
tests.py
file之外的任何地方使用它,很难知道您在做什么。你应该分别测试这些东西;调用
update_queue
并模拟任务以断言它被调用后,直接调用任务并检查队列。注意,您不应该更改电子邮件后端。
from django.core.mail import EmailMessage
from django.template.loader import render_to_string
from django.contrib.auth import get_user_model

from accounts.models import Token
from celery import shared_task

@shared_task(bind=True)
def send_login_email_task(self, email):
    try:
        uid = str(uuid.uuid4())
        Token.objects.create(email=email, uid=uid)
        current_site = 'localhost:8000'
        mail_subject = 'Activate your account.'
        message = render_to_string('accounts/login_activation_email.html', {
            'domain': current_site,
            'uid': uid
        })
        print('called')
        email = EmailMessage(mail_subject, message, to=[email])
        email.send()
    except Token.DoesNotExist:
        logging.warning(
            "Tried to send activation email to non-existing user '%s'", email)
    except smtplib.SMTPException as exc:
        raise self.retry(exc=exc)
from django.test import TestCase
from unittest.mock import patch
from django.contrib.auth import get_user_model
from celery.exceptions import Retry
from proj.celery import App
import smtplib
import uuid


import accounts.tasks
from accounts.models import Token
@patch('accounts.tasks.EmailMessage')
def test_send_login_email_task(self, mock_email_message):

    # call task
    token = Token.objects.get(email=self.email, uid=self.uid)
    print(token.email)
    accounts.tasks.send_login_email_task.apply_async((token.email,))
    self.assertEqual(mock_email_message.called, True)

    # patch EmailMessage
    print(mock_email_message.call_args)
    args, kwargs = mock_email_message.call_args
    subject = args[0]
    self.assertEqual(subject, 'Activate your account.')
    self.assertEqual(kwargs, {'to': ['ama@example.com']})