mock复杂依赖时patch路径须写被调用处的完整模块路径,如app.services.order_processor.PaymentClient;应分层构造fixture模拟依赖链,用side_effect模拟多态响应,并必须断言调用次数、参数及属性。

mock 复杂类依赖时,patch 路径必须写对被调用位置
mock 失效最常见的原因,就是 patch 的字符串路径写错了——不是写“你从哪 import 的”,而是写“被测代码里实际访问依赖的模块路径”。比如被测函数在 app.services.order_processor 里写了 PaymentClient().charge(),那你要 patch 的是 app.services.order_processor.PaymentClient,而不是 payments.client.PaymentClient 或 tests.test_order.PaymentClient。
容易踩的坑:
- 路径中漏掉包层级,比如写成
'services.order_processor.PaymentClient'(缺app.) - 把类实例方法当成类方法 patch,比如想 mock
user_repo.find_by_id(123),却去 patchuser_repository.UserRepository.find_by_id,而正确位置是app.handlers.user_handler.user_repo.find_by_id - 在 pytest 中用了 autouse fixture 自动 patch,但路径没随测试模块变化,导致跨文件失效
别 patch __init__,用 fixture 组装 mock 实例链
硬 patch 类的 __init__ 几乎总是错的:mock 后实例属性(如 self.db、self.config)根本不会初始化,后续一访问就抛 AttributeError。真实依赖往往是“类 A 依赖类 B,B 又依赖类 C”,这时该分层构造。
推荐做法:
立即学习“Python免费学习笔记(深入)”;
- 用
@pytest.fixture单独定义mock_db、mock_logger等基础依赖 - 再写一个
@pytest.fixture把它们组合成mock_user_service,例如UserService(db=mock_db, logger=mock_logger) - 测试函数直接接收
mock_user_service作为参数,不再 patch 任何东西 - 需要轻量行为时,直接返回真实对象(如内存 SQLite),而非强行 mock 所有方法
side_effect 是模拟多态响应的关键,return_value 不够用
return_value 只能返回固定值,但真实依赖常需不同调用返回不同结果:第一次成功、第二次超时、第三次抛异常。这时候必须用 side_effect,它和 return_value 互斥——设了前者,后者自动忽略。
常用模式:
- 传列表:
mock_api.fetch.side_effect = [{'id': 1}, None, ConnectionError('timeout')] - 传函数:
mock_config.get.side_effect = lambda key: 'prod' if key == 'ENV' else '8000' - 传异常类:
mock_storage.save.side_effect = PermissionError - 注意:如果 mock 的是构造函数(如
SomeClient()),要设置mock_client.return_value.method.side_effect,而不是直接设mock_client.side_effect
不 assert 调用,等于没 mock
只写 @patch 不验证调用,测试就只是“跑过”,根本起不到保障作用。漏掉一次调用、参数拼错、分支没进,全都会逃逸。
必须做的断言:
- 是否被调用:
mock_api.post.assert_called()或.assert_called_once() - 调用参数是否正确:
mock_api.post.assert_called_with(url='https://api.x', json={'x': 1}) - 多次调用顺序与参数:
from unittest.mock import call; mock_api.get.assert_has_calls([call('/a'), call('/b')]) - 特别注意:如果被测逻辑里用了
if response.status == 200:,那光 mock 返回值不够,还得 mockresponse.status属性,例如mock_resp.status = 200
复杂点在于,真实业务中依赖往往嵌套深、路径长、调用条件多;最容易被忽略的是「mock 对象的属性也需要显式设置」——比如 mock 了一个 HTTP 响应对象,却忘了设 .json 方法或 .status_code 属性,结果测试里一读就 AttributeError。


















