Python单元测试使用@patch时如何控制Mock对象的合法返回值?
问题复现代码
def create(user): user.password = hash_function(user.password) db.add(user) @patch('hash_function') def test_create_user(hash_function): create(User()) hash_function.assert_called()
问题解答
@patch是Python单元测试生态中经过大量生产验证的测试隔离工具,本身是安全的。你遇到的报错并非工具设计缺陷,而是没有对Mock对象的默认行为做符合业务逻辑的配置导致的。
被@patch替换生成的Mock对象默认行为是:无论你以什么参数调用它、访问它的任何属性,都会返回一个新的Mock实例。如果被测代码的后续逻辑会使用这个返回值做类型校验、业务逻辑处理,默认返回的Mock实例自然无法通过校验,触发报错。
你可以通过以下几种常用方式配置Mock的合法返回值,规避这类问题:
直接指定固定返回值(最常用)
如果你不需要真实执行hash_function的逻辑,只需要它返回一个符合后续校验要求的合法值,直接给Mock的return_value属性赋值即可:@patch('your_module.hash_function') # 注意替换为hash_function在被测文件中的实际导入路径 def test_create_user(hash_function): # 配置返回符合password字段格式要求的合法值,通过db.add的参数校验 hash_function.return_value = "valid_hashed_password_str" create(User()) hash_function.assert_called()通过wraps参数保留原函数行为
如果你不想手动构造返回值,希望Mock在记录调用信息(用于断言)的同时,依然执行原函数的真实逻辑返回真实结果,可以给patch传入wraps参数指向原函数:@patch('your_module.hash_function', wraps=hash_function) def test_create_user(hash_function): create(User()) # 依然可以正常做调用断言 hash_function.assert_called()这种写法下
hash_function会真实运行返回合法的哈希值,完全不会触发db.add的参数校验错误。通过side_effect实现自定义逻辑
如果你需要在Mock中自定义入参校验、动态返回值逻辑,可以给side_effect传入一个和原函数签名一致的函数,Mock被调用时会执行这个自定义函数,用它的返回值作为Mock的返回结果:@patch('your_module.hash_function') def test_create_user(hash_function): def real_mock_hash(raw_password): # 可以在这里自定义入参校验逻辑 assert isinstance(raw_password, str) # 返回符合要求的结果 return f"hashed_{raw_password}" hash_function.side_effect = real_mock_hash test_user = User(password="test_raw_pwd") create(test_user) hash_function.assert_called_once_with("test_raw_pwd")
Mock使用注意事项
- 做单元测试时应该完整隔离外部依赖,比如示例中的
db.add属于数据库层外部依赖,本身也应该被Mock替换,这种情况下你甚至不需要关心user.password的具体值,因为Mock的db.add不会执行真实的参数校验逻辑,测试运行会更稳定。 - 不要滥用
@patch,只对当前测试用例需要隔离的、不属于当前测试单元的依赖做替换,避免Mock范围过大导致测试逻辑和真实业务逻辑偏离。 - 配置Mock返回值时尽量贴合业务代码的参数校验规则,比如要求字符串类型就返回字符串,要求数值类型就返回对应数值,不要保留默认的Mock实例传入有严格校验的逻辑分支。
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

