用pytest模拟导入的功能 [英] Mocking a imported function with pytest

查看:122
本文介绍了用pytest模拟导入的功能的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想测试我编写的电子邮件发送方法.在文件format_email.py中,我导入了send_email.

I would like to test a email sending method I wrote. In file, format_email.py I import send_email.

 from cars.lib.email import send_email

 class CarEmails(object):

    def __init__(self, email_client, config):
        self.email_client = email_client
        self.config = config

    def send_cars_email(self, recipients, input_payload):

在send_cars_email()中格式化电子邮件内容之后,我使用之前导入的方法发送电子邮件.

After formatting the email content in send_cars_email() I send the email using the method I imported earlier.

 response_code = send_email(data, self.email_client)

在我的测试文件test_car_emails.py中

in my test file test_car_emails.py

@pytest.mark.parametrize("test_input,expected_output", test_data)
def test_email_payload_formatting(test_input, expected_output):
    emails = CarsEmails(email_client=MagicMock(), config=config())
    emails.send_email = MagicMock()
    emails.send_cars_email(*test_input)
    emails.send_email.assert_called_with(*expected_output)

当我运行测试时,它在未调用断言时失败.我相信问题出在我嘲笑send_email函数的地方.

When I run the test it fails on assertion not called. I believe The issue is where I am mocking the send_email function.

我应该在哪里嘲笑此功能?

Where should I be mocking this function?

推荐答案

emails.send_email = MagicMock()行嘲笑的是函数

class CarsEmails:

    def send_email(self):
        ...

您没有的.因此,此行只会为emails对象 add 添加新功能.但是,该函数不会从您的代码中调用,并且赋值将完全无效.相反,您应该从cars.lib.email模块中模拟函数send_email.

that you don't have. This line will thus only add a new function to your emails object. However, this function is not called from your code and the assignment will have no effect at all. Instead, you should mock the function send_email from the cars.lib.email module.

一旦通过模块format_email.py中的from cars.lib.email import send_email导入了功能send_email,该功能将以名称format_email.send_email可用.由于您知道该函数已在此处调用,因此可以使用其新名称对其进行模拟:

Once you have imported the function send_email via from cars.lib.email import send_email in your module format_email.py, it becomes available under the name format_email.send_email. Since you know the function is called there, you can mock it under its new name:

from unittest.mock import patch

from format_email import CarsEmails

@pytest.mark.parametrize("test_input,expected_output", test_data)
def test_email_payload_formatting(config, test_input, expected_output):
    emails = CarsEmails(email_client=MagicMock(), config=config)
    with patch('format_email.send_email') as mocked_send:
        emails.send_cars_email(*test_input)
        mocked_send.assert_called_with(*expected_output)

模拟定义的功能

更新:

阅读在何处unittest文档中的补丁程序(另请参见评论来自 Martijn Pieters 建议):

It really helps to read the section Where to patch in the unittest docs (also see the comment from Martijn Pieters suggesting it):

基本原理是,您修补查找对象的位置,该对象不一定与定义对象的位置相同.

The basic principle is that you patch where an object is looked up, which is not necessarily the same place as where it is defined.

因此,请在使用位置坚持使用该函数的模拟方法,而不要先刷新导入或以正确的顺序对齐它们.即使format_email的源代码由于某种原因而无法访问(例如,它是经过Cythonized/编译的C/C ++扩展模块)时,即使有一些晦涩的用例,您仍然只有两种可能的导入方式,因此,只需尝试在何处描述的两种模拟方法补丁并使用成功的补丁.

So stick with the mocking of the function in usage places and don't start with refreshing the imports or aligning them in correct order. Even when there should be some obscure usecase when the source code of format_email would be inaccessible for some reason (like when it is a cythonized/compiled C/C++ extension module), you still have only two possible ways of doing the import, so just try out both mocking possibilities as described in Where to patch and use the one that succeeds.

原始答案:

您还可以在其原始模块中模拟send_email函数:

You can also mock send_email function in its original module:

with patch('cars.lib.email.send_email') as mocked_send:
    ...

,但是请注意,如果您在修补之前在format_email.py中调用了send_email的导入,则由于已经导入了该功能,因此修补cars.lib.emailformat_email中的代码不会产生任何影响.在下面的示例中,mocked_send不会被调用:

but be aware that if you have called the import of send_email in format_email.py before the patching, patching cars.lib.email won't have any effect on code in format_email since the function is already imported, so the mocked_send in the example below won't be called:

from format_email import CarsEmails

...

emails = CarsEmails(email_client=MagicMock(), config=config)
with patch('cars.lib.email.send_email') as mocked_send:
    emails.send_cars_email(*test_input)
    mocked_send.assert_called_with(*expected_output)

要解决此问题,您应该在cars.lib.email的修补程序之后首次导入format_email:

To fix that, you should either import format_email for the first time after the patch of cars.lib.email:

with patch('cars.lib.email.send_email') as mocked_send:
    from format_email import CarsEmails
    emails = CarsEmails(email_client=MagicMock(), config=config)
    emails.send_cars_email(*test_input)
    mocked_send.assert_called_with(*expected_output)

或重新加载模块,例如与importlib.reload():

or reload the module e.g. with importlib.reload():

import importlib

import format_email

with patch('cars.lib.email.send_email') as mocked_send:
    importlib.reload(format_email)
    emails = format_email.CarsEmails(email_client=MagicMock(), config=config)
    emails.send_cars_email(*test_input)
    mocked_send.assert_called_with(*expected_output)

如果您问我,这两种方法都不是很好.我会坚持在称为它的模块中模拟该函数.

Not that pretty either way, if you ask me. I'd stick with mocking the function in the module where it is called.

这篇关于用pytest模拟导入的功能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆