mock.patch smtplib.SMTP 的正确方法

Correct way to mock.patch smtplib.SMTP

正在尝试 mock.patch 在单元测试中调用 smtplib.SMTP.sendmailsendmail 方法似乎已成功模拟,我们可以将其查询为 MagicMock,但 sendmail 模拟的 calledcalled_args 属性未正确更新。看来我没有正确应用补丁。

这是我正在尝试的一个简化示例:

import unittest.mock
with unittest.mock.patch('smtplib.SMTP', autospec=True) as mock:
    import smtplib
    smtp = smtplib.SMTP('localhost')
    smtp.sendmail('me', 'me', 'hello world\n')
    mock.assert_called()           # <--- this succeeds
    mock.sendmail.assert_called()  # <--- this fails

此示例生成:

AssertionError: Expected 'sendmail' to have been called.

如果我将补丁更改为 smtp.SMTP.sendmail;例如:

with unittest.mock.patch('smtplib.SMTP.sendmail.', autospec=True) as mock:
    ...

在这种情况下,我可以成功访问 mock 的 called_argscalled 属性,但是因为允许进行 smtplib.SMTP 初始化,所以实际的 smtp 会话是与主机建立。这是单元测试,我不希望进行实际的联网。

我今天遇到了同样的问题,忘记了我正在使用上下文,所以只需更改

mock.sendmail.assert_called()

mock.return_value.__enter__.return_value.sendmail.assert_called()

这看起来很乱,但这是我的例子:

msg = EmailMessage()
msg['From'] = 'no@no.com'
msg['To'] = 'no@no.com'
msg['Subject'] = 'subject'
msg.set_content('content');

with patch('smtplib.SMTP', autospec=True) as mock_smtp:
    misc.send_email(msg)

    mock_smtp.assert_called()

    context = mock_smtp.return_value.__enter__.return_value
    context.ehlo.assert_called()
    context.starttls.assert_called()
    context.login.assert_called()
    context.send_message.assert_called_with(msg)

我将 dustymugs 的 post 标记为答案,但我发现了另一种技术来单元测试依赖于 mocks 的调用 method_calls

import unittest.mock
with unittest.mock.patch('smtplib.SMTP', autospec=True) as mock:
    import smtplib
    smtp = smtplib.SMTP('localhost')
    smtp.sendmail('me', 'you', 'hello world\n')

    # Validate sendmail() was called
    name, args, kwargs = smtpmock.method_calls.pop(0)
    self.assertEqual(name, '().sendmail')
    self.assertEqual({}, kwargs)

    # Validate the sendmail() parameters
    from_, to_, body_ = args
    self.assertEqual('me', from_)
    self.assertEqual(['you'], to_)
    self.assertIn('hello world', body_)