use*_*855 4 python mocking pytest magicmock
我想测试我写的电子邮件发送方法。在文件 format_email.py 中,我导入了 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):
Run Code Online (Sandbox Code Playgroud)
在 send_cars_email() 中格式化电子邮件内容后,我使用之前导入的方法发送电子邮件。
response_code = send_email(data, self.email_client)
Run Code Online (Sandbox Code Playgroud)
在我的测试文件 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)
Run Code Online (Sandbox Code Playgroud)
当我运行测试时,它在未调用断言时失败。我相信问题在于我嘲笑 send_email 函数的地方。
我应该在哪里嘲笑这个功能?
你用这条线嘲笑的emails.send_email = MagicMock()是函数
class CarsEmails:
def send_email(self):
...
Run Code Online (Sandbox Code Playgroud)
你没有的。因此,这一行只会向您的对象添加一个新函数emails。但是,此函数不会从您的代码中调用,并且分配将根本不起作用。相反,你应该嘲笑功能send_email从cars.lib.email模块。
一旦您在模块中send_email通过 via导入了该函数,它就会在名称下可用。由于您知道该函数在那里被调用,您可以用它的新名称模拟它:from cars.lib.email import send_emailformat_email.pyformat_email.send_email
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)
Run Code Online (Sandbox Code Playgroud)
更新:
它确实有助于阅读部分在哪里补丁的unittest文档(另见注释从的Martijn Pieters的提示吧):
基本原则是在查找对象的位置打补丁,该位置不一定与定义的位置相同。
所以坚持在使用位置模拟函数,不要从刷新导入或以正确的顺序对齐它们开始。即使format_email由于某些原因(例如当它是一个 cythonized/编译的 C/C++ 扩展模块时)的源代码无法访问时,应该有一些晦涩的用例,您仍然只有两种可能的导入方式,所以请尝试排除两种模拟可能性,如在哪里打补丁和使用成功的那个。
原答案:
您还可以send_email在其原始模块中模拟函数:
with patch('cars.lib.email.send_email') as mocked_send:
...
Run Code Online (Sandbox Code Playgroud)
但请注意,如果您在修补之前调用了send_emailin的导入,由于函数已经导入,因此format_email.py修补cars.lib.email不会对代码产生任何影响format_email,因此mocked_send不会调用下面示例中的 :
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)
Run Code Online (Sandbox Code Playgroud)
要解决此问题,您应该format_email在以下补丁后第一次导入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)
Run Code Online (Sandbox Code Playgroud)
或重新加载模块,例如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)
Run Code Online (Sandbox Code Playgroud)
如果你问我的话,这两种方式都不是那么漂亮。我会坚持在调用它的模块中模拟该函数。
小智 6
由于您使用的是 pytest,我建议使用 pytest 的内置“monkeypatch”装置。
考虑这个简单的设置:
我们定义了要模拟的函数。
"""`my_library.py` defining 'foo'."""
def foo(*args, **kwargs):
"""Some function that we're going to mock."""
return args, kwargs
Run Code Online (Sandbox Code Playgroud)
并在一个单独的文件中调用该函数的类。
"""`my_module` defining MyClass."""
from my_library import foo
class MyClass:
"""Some class used to demonstrate mocking imported functions."""
def should_call_foo(self, *args, **kwargs):
return foo(*args, **kwargs)
Run Code Online (Sandbox Code Playgroud)
我们使用 'monkeypatch' 夹具模拟使用 它的函数
"""`test_my_module.py` testing MyClass from 'my_module.py'"""
from unittest.mock import Mock
import pytest
from my_module import MyClass
def test_mocking_foo(monkeypatch):
"""Mock 'my_module.foo' and test that it was called by the instance of
MyClass.
"""
my_mock = Mock()
monkeypatch.setattr('my_module.foo', my_mock)
MyClass().should_call_foo(1, 2, a=3, b=4)
my_mock.assert_called_once_with(1, 2, a=3, b=4)
Run Code Online (Sandbox Code Playgroud)
如果你想重用它,我们也可以将模拟分解到它自己的夹具中。
@pytest.fixture
def mocked_foo(monkeypatch):
"""Fixture that will mock 'my_module.foo' and return the mock."""
my_mock = Mock()
monkeypatch.setattr('my_module.foo', my_mock)
return my_mock
def test_mocking_foo_in_fixture(mocked_foo):
"""Using the 'mocked_foo' fixture to test that 'my_module.foo' was called
by the instance of MyClass."""
MyClass().should_call_foo(1, 2, a=3, b=4)
mocked_foo.assert_called_once_with(1, 2, a=3, b=4)
Run Code Online (Sandbox Code Playgroud)