如何用@patch.object mock模块函数作用域外导入的Logger类方法?
如何Mock模块全局作用域中Logger类的__init__和write方法
我正在尝试用@patch.object来MockLogger类的__init__方法以及log.write()方法,但这个类是在模块全局作用域导入并使用的,不是在函数内部。之前看到的教程都是针对函数内使用的场景打补丁,现在需要同时Mockmain.py和routers.py里的Logger行为,该怎么做?
相关代码文件
src/apis/main.py
from utils.log import Logger from routes import route log = Logger(name="logger-1") log.write("logger started") def main(): log = Logger(name="logger-1") log.write("inside main") route() if __name__ == "__main__": import logging logging.basicConfig(level=logging.INFO) # for demo main()
src/apis/routers/routes.py
from utils.log import Logger log = Logger(name="logger-1") log.write(message=f"Inside route") def route(): log.write(message=f"Logging done.")
utils/log/logging.py
import logging class Logger: def __init__(self, name): # 需要被Mock的初始化逻辑 pass def write(self, message): # 需要被Mock为返回None的方法 pass
解决方案
核心思路
打补丁的关键是针对目标被引用的位置,而非它的定义位置。对于全局作用域初始化的log实例,有两种处理方式:要么在模块导入前完成补丁,让全局实例直接是Mock对象;要么在模块导入后,单独Mock已存在的全局实例的方法。
方案一:Mock模块中的Logger类(覆盖全局与函数内实例)
通过@patch直接Mock目标模块中导入的Logger类,同时处理已初始化的全局log实例,实现全场景Mock。
示例测试代码(假设测试文件为tests/test_main.py):
from unittest.mock import patch, MagicMock import src.apis.main as main_module import src.apis.routers.routes as routes_module def test_main_and_route_flow(): # 分别Mock两个模块中的Logger类 with patch('src.apis.main.Logger') as mock_main_logger, \ patch('src.apis.routers.routes.Logger') as mock_route_logger: # 配置Mock实例的行为:__init__不做实际操作,write返回None mock_main_instance = MagicMock() mock_main_instance.write.return_value = None mock_main_logger.return_value = mock_main_instance mock_route_instance = MagicMock() mock_route_instance.write.return_value = None mock_route_logger.return_value = mock_route_instance # 由于模块已提前导入,全局的log实例是原始Logger对象,需要单独Mock它的write方法 main_module.log.write = MagicMock(return_value=None) routes_module.log.write = MagicMock(return_value=None) # 执行测试逻辑 main_module.main() # 验证全局作用域的write调用 main_module.log.write.assert_called_once_with("logger started") routes_module.log.write.assert_any_call(message="Inside route") # 验证函数内创建的Logger实例的write调用 mock_main_instance.write.assert_called_once_with("inside main") # 验证route函数内的write调用 routes_module.log.write.assert_any_call(message="Logging done.")
方案二:提前补丁模块(避免全局实例提前初始化)
如果模块在导入阶段就执行了全局的log.write,可以在测试文件开头先打补丁,确保全局实例创建时直接是Mock对象。
from unittest.mock import patch, MagicMock # 在导入目标模块前,先对main.py的Logger打补丁 with patch('src.apis.main.Logger') as mock_main_logger: mock_main_instance = MagicMock() mock_main_instance.write.return_value = None mock_main_logger.return_value = mock_main_instance import src.apis.main as main_module # 同样提前对routes.py的Logger打补丁 with patch('src.apis.routers.routes.Logger') as mock_route_logger: mock_route_instance = MagicMock() mock_route_instance.write.return_value = None mock_route_logger.return_value = mock_route_instance import src.apis.routers.routes as routes_module def test_full_log_flow(): # 执行测试逻辑 main_module.main() # 验证所有write调用 mock_main_instance.write.assert_any_call("logger started") mock_main_instance.write.assert_any_call("inside main") mock_route_instance.write.assert_any_call(message="Inside route") mock_route_instance.write.assert_any_call(message="Logging done.")
关键注意事项
- 补丁路径必须准确:必须使用目标模块中导入Logger的路径(如
src.apis.main.Logger),而非Logger的定义路径(utils.log.logging.Logger)。 - 全局实例的特殊处理:若模块已提前导入,全局
log实例是原始对象,需单独Mock其方法;若在导入前打补丁,全局实例会直接是Mock对象。 - 统一Mock行为:可以复用同一个MagicMock实例,统一两个模块的Logger行为,减少重复代码。
内容的提问来源于stack exchange,提问作者Amogh Mishra
相关产品推荐
相关产品推荐

